home *** CD-ROM | disk | FTP | other *** search
/ ftp.editplus.com / 2015-02-07.ftp.editplus.com.tar / ftp.editplus.com / epp370p1512_1212.exe / [0] / zen_coding_epp.js < prev   
Text File  |  2013-02-12  |  222KB  |  7,828 lines

  1. /**
  2.  * Zen Coding settings
  3.  * @author Sergey Chikuyonok (serge.che@gmail.com)
  4.  * @link http://chikuyonok.ru
  5.  */
  6. var zen_settings = {
  7.     /** 
  8.      * Variables that can be placed inside snippets or abbreviations as ${variable}
  9.      * ${child} variable is reserved, don't use it 
  10.      */
  11.     'variables': {
  12.         'lang': 'en',
  13.         'locale': 'en-US',
  14.         'charset': 'UTF-8',
  15.         
  16.         /** Inner element indentation */
  17.         'indentation': '\t',
  18.         
  19.         // newline variables, useful for wrapping
  20.         'newline': '\n',
  21.         'nl': '\n'
  22.     },
  23.     
  24.     'css': {
  25.         'filters': 'html,css',
  26.         'snippets': {
  27.             "@i": "@import url(|);",
  28.             "@m": "@media print {\n\t|\n}",
  29.             "@f": "@font-face {\n\tfont-family:|;\n\tsrc:url(|);\n}",
  30.             "!": "!important",
  31.             "pos": "position:|;",
  32.             "pos:s": "position:static;",
  33.             "pos:a": "position:absolute;",
  34.             "pos:r": "position:relative;",
  35.             "pos:f": "position:fixed;",
  36.             "t": "top:|;",
  37.             "t:a": "top:auto;",
  38.             "r": "right:|;",
  39.             "r:a": "right:auto;",
  40.             "b": "bottom:|;",
  41.             "b:a": "bottom:auto;",
  42.             "brad": "-webkit-border-radius: ${1:radius};\n-moz-border-radius: $1;\n-ms-border-radius: $1;\nborder-radius: $1;",
  43.             "bsha": "-webkit-box-shadow: ${1:hoff} ${2:voff} ${3:blur} ${4:rgba(0,0,0,0.5)};\n-moz-box-shadow: $1 $2 $3 $4;\n-ms-box-shadow: $1 $2 $3 $4;\nbox-shadow: $1 $2 $3 $4;",
  44.             "l": "left:|;",
  45.             "l:a": "left:auto;",
  46.             "z": "z-index:|;",
  47.             "z:a": "z-index:auto;",
  48.             "fl": "float:|;",
  49.             "fl:n": "float:none;",
  50.             "fl:l": "float:left;",
  51.             "fl:r": "float:right;",
  52.             "cl": "clear:|;",
  53.             "cl:n": "clear:none;",
  54.             "cl:l": "clear:left;",
  55.             "cl:r": "clear:right;",
  56.             "cl:b": "clear:both;",
  57.             "d": "display:|;",
  58.             "d:n": "display:none;",
  59.             "d:b": "display:block;",
  60.             "d:i": "display:inline;",
  61.             "d:ib": "display:inline-block;",
  62.             "d:li": "display:list-item;",
  63.             "d:ri": "display:run-in;",
  64.             "d:cp": "display:compact;",
  65.             "d:tb": "display:table;",
  66.             "d:itb": "display:inline-table;",
  67.             "d:tbcp": "display:table-caption;",
  68.             "d:tbcl": "display:table-column;",
  69.             "d:tbclg": "display:table-column-group;",
  70.             "d:tbhg": "display:table-header-group;",
  71.             "d:tbfg": "display:table-footer-group;",
  72.             "d:tbr": "display:table-row;",
  73.             "d:tbrg": "display:table-row-group;",
  74.             "d:tbc": "display:table-cell;",
  75.             "d:rb": "display:ruby;",
  76.             "d:rbb": "display:ruby-base;",
  77.             "d:rbbg": "display:ruby-base-group;",
  78.             "d:rbt": "display:ruby-text;",
  79.             "d:rbtg": "display:ruby-text-group;",
  80.             "v": "visibility:|;",
  81.             "v:v": "visibility:visible;",
  82.             "v:h": "visibility:hidden;",
  83.             "v:c": "visibility:collapse;",
  84.             "ov": "overflow:|;",
  85.             "ov:v": "overflow:visible;",
  86.             "ov:h": "overflow:hidden;",
  87.             "ov:s": "overflow:scroll;",
  88.             "ov:a": "overflow:auto;",
  89.             "ovx": "overflow-x:|;",
  90.             "ovx:v": "overflow-x:visible;",
  91.             "ovx:h": "overflow-x:hidden;",
  92.             "ovx:s": "overflow-x:scroll;",
  93.             "ovx:a": "overflow-x:auto;",
  94.             "ovy": "overflow-y:|;",
  95.             "ovy:v": "overflow-y:visible;",
  96.             "ovy:h": "overflow-y:hidden;",
  97.             "ovy:s": "overflow-y:scroll;",
  98.             "ovy:a": "overflow-y:auto;",
  99.             "ovs": "overflow-style:|;",
  100.             "ovs:a": "overflow-style:auto;",
  101.             "ovs:s": "overflow-style:scrollbar;",
  102.             "ovs:p": "overflow-style:panner;",
  103.             "ovs:m": "overflow-style:move;",
  104.             "ovs:mq": "overflow-style:marquee;",
  105.             "zoo": "zoom:1;",
  106.             "cp": "clip:|;",
  107.             "cp:a": "clip:auto;",
  108.             "cp:r": "clip:rect(|);",
  109.             "bxz": "box-sizing:|;",
  110.             "bxz:cb": "box-sizing:content-box;",
  111.             "bxz:bb": "box-sizing:border-box;",
  112.             "bxsh": "box-shadow:|;",
  113.             "bxsh:n": "box-shadow:none;",
  114.             "bxsh:w": "-webkit-box-shadow:0 0 0 #000;",
  115.             "bxsh:m": "-moz-box-shadow:0 0 0 0 #000;",
  116.             "m": "margin:|;",
  117.             "m:a": "margin:auto;",
  118.             "m:0": "margin:0;",
  119.             "m:2": "margin:0 0;",
  120.             "m:3": "margin:0 0 0;",
  121.             "m:4": "margin:0 0 0 0;",
  122.             "mt": "margin-top:|;",
  123.             "mt:a": "margin-top:auto;",
  124.             "mr": "margin-right:|;",
  125.             "mr:a": "margin-right:auto;",
  126.             "mb": "margin-bottom:|;",
  127.             "mb:a": "margin-bottom:auto;",
  128.             "ml": "margin-left:|;",
  129.             "ml:a": "margin-left:auto;",
  130.             "p": "padding:|;",
  131.             "p:0": "padding:0;",
  132.             "p:2": "padding:0 0;",
  133.             "p:3": "padding:0 0 0;",
  134.             "p:4": "padding:0 0 0 0;",
  135.             "pt": "padding-top:|;",
  136.             "pr": "padding-right:|;",
  137.             "pb": "padding-bottom:|;",
  138.             "pl": "padding-left:|;",
  139.             "w": "width:|;",
  140.             "w:a": "width:auto;",
  141.             "h": "height:|;",
  142.             "h:a": "height:auto;",
  143.             "maw": "max-width:|;",
  144.             "maw:n": "max-width:none;",
  145.             "mah": "max-height:|;",
  146.             "mah:n": "max-height:none;",
  147.             "miw": "min-width:|;",
  148.             "mih": "min-height:|;",
  149.             "o": "outline:|;",
  150.             "o:n": "outline:none;",
  151.             "oo": "outline-offset:|;",
  152.             "ow": "outline-width:|;",
  153.             "os": "outline-style:|;",
  154.             "oc": "outline-color:#000;",
  155.             "oc:i": "outline-color:invert;",
  156.             "bd": "border:|;",
  157.             "bd+": "border:1px solid #000;",
  158.             "bd:n": "border:none;",
  159.             "bdbk": "border-break:|;",
  160.             "bdbk:c": "border-break:close;",
  161.             "bdcl": "border-collapse:|;",
  162.             "bdcl:c": "border-collapse:collapse;",
  163.             "bdcl:s": "border-collapse:separate;",
  164.             "bdc": "border-color:#000;",
  165.             "bdi": "border-image:url(|);",
  166.             "bdi:n": "border-image:none;",
  167.             "bdi:w": "-webkit-border-image:url(|) 0 0 0 0 stretch stretch;",
  168.             "bdi:m": "-moz-border-image:url(|) 0 0 0 0 stretch stretch;",
  169.             "bdti": "border-top-image:url(|);",
  170.             "bdti:n": "border-top-image:none;",
  171.             "bdri": "border-right-image:url(|);",
  172.             "bdri:n": "border-right-image:none;",
  173.             "bdbi": "border-bottom-image:url(|);",
  174.             "bdbi:n": "border-bottom-image:none;",
  175.             "bdli": "border-left-image:url(|);",
  176.             "bdli:n": "border-left-image:none;",
  177.             "bdci": "border-corner-image:url(|);",
  178.             "bdci:n": "border-corner-image:none;",
  179.             "bdci:c": "border-corner-image:continue;",
  180.             "bdtli": "border-top-left-image:url(|);",
  181.             "bdtli:n": "border-top-left-image:none;",
  182.             "bdtli:c": "border-top-left-image:continue;",
  183.             "bdtri": "border-top-right-image:url(|);",
  184.             "bdtri:n": "border-top-right-image:none;",
  185.             "bdtri:c": "border-top-right-image:continue;",
  186.             "bdbri": "border-bottom-right-image:url(|);",
  187.             "bdbri:n": "border-bottom-right-image:none;",
  188.             "bdbri:c": "border-bottom-right-image:continue;",
  189.             "bdbli": "border-bottom-left-image:url(|);",
  190.             "bdbli:n": "border-bottom-left-image:none;",
  191.             "bdbli:c": "border-bottom-left-image:continue;",
  192.             "bdf": "border-fit:|;",
  193.             "bdf:c": "border-fit:clip;",
  194.             "bdf:r": "border-fit:repeat;",
  195.             "bdf:sc": "border-fit:scale;",
  196.             "bdf:st": "border-fit:stretch;",
  197.             "bdf:ow": "border-fit:overwrite;",
  198.             "bdf:of": "border-fit:overflow;",
  199.             "bdf:sp": "border-fit:space;",
  200.             "bdl": "border-length:|;",
  201.             "bdl:a": "border-length:auto;",
  202.             "bdsp": "border-spacing:|;",
  203.             "bds": "border-style:|;",
  204.             "bds:n": "border-style:none;",
  205.             "bds:h": "border-style:hidden;",
  206.             "bds:dt": "border-style:dotted;",
  207.             "bds:ds": "border-style:dashed;",
  208.             "bds:s": "border-style:solid;",
  209.             "bds:db": "border-style:double;",
  210.             "bds:dtds": "border-style:dot-dash;",
  211.             "bds:dtdtds": "border-style:dot-dot-dash;",
  212.             "bds:w": "border-style:wave;",
  213.             "bds:g": "border-style:groove;",
  214.             "bds:r": "border-style:ridge;",
  215.             "bds:i": "border-style:inset;",
  216.             "bds:o": "border-style:outset;",
  217.             "bdw": "border-width:|;",
  218.             "bdt": "border-top:|;",
  219.             "bdt+": "border-top:1px solid #000;",
  220.             "bdt:n": "border-top:none;",
  221.             "bdtw": "border-top-width:|;",
  222.             "bdts": "border-top-style:|;",
  223.             "bdts:n": "border-top-style:none;",
  224.             "bdtc": "border-top-color:#000;",
  225.             "bdr": "border-right:|;",
  226.             "bdr+": "border-right:1px solid #000;",
  227.             "bdr:n": "border-right:none;",
  228.             "bdrw": "border-right-width:|;",
  229.             "bdrs": "border-right-style:|;",
  230.             "bdrs:n": "border-right-style:none;",
  231.             "bdrc": "border-right-color:#000;",
  232.             "bdb": "border-bottom:|;",
  233.             "bdb+": "border-bottom:1px solid #000;",
  234.             "bdb:n": "border-bottom:none;",
  235.             "bdbw": "border-bottom-width:|;",
  236.             "bdbs": "border-bottom-style:|;",
  237.             "bdbs:n": "border-bottom-style:none;",
  238.             "bdbc": "border-bottom-color:#000;",
  239.             "bdl": "border-left:|;",
  240.             "bdl+": "border-left:1px solid #000;",
  241.             "bdl:n": "border-left:none;",
  242.             "bdlw": "border-left-width:|;",
  243.             "bdls": "border-left-style:|;",
  244.             "bdls:n": "border-left-style:none;",
  245.             "bdlc": "border-left-color:#000;",
  246.             "bdrs": "border-radius:|;",
  247.             "bdtrrs": "border-top-right-radius:|;",
  248.             "bdtlrs": "border-top-left-radius:|;",
  249.             "bdbrrs": "border-bottom-right-radius:|;",
  250.             "bdblrs": "border-bottom-left-radius:|;",
  251.             "bg": "background:|;",
  252.             "bg+": "background:#FFF url(|) 0 0 no-repeat;",
  253.             "bg:n": "background:none;",
  254.             "bg:ie": "filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='${1:x}.png',sizingMethod='${2:crop}');",
  255.             "bgc": "background-color:#FFF;",
  256.             "bgi": "background-image:url(|);",
  257.             "bgi:n": "background-image:none;",
  258.             "bgr": "background-repeat:|;",
  259.             "bgr:n": "background-repeat:no-repeat;",
  260.             "bgr:x": "background-repeat:repeat-x;",
  261.             "bgr:y": "background-repeat:repeat-y;",
  262.             "bga": "background-attachment:|;",
  263.             "bga:f": "background-attachment:fixed;",
  264.             "bga:s": "background-attachment:scroll;",
  265.             "bgp": "background-position:0 0;",
  266.             "bgpx": "background-position-x:|;",
  267.             "bgpy": "background-position-y:|;",
  268.             "bgbk": "background-break:|;",
  269.             "bgbk:bb": "background-break:bounding-box;",
  270.             "bgbk:eb": "background-break:each-box;",
  271.             "bgbk:c": "background-break:continuous;",
  272.             "bgcp": "background-clip:|;",
  273.             "bgcp:bb": "background-clip:border-box;",
  274.             "bgcp:pb": "background-clip:padding-box;",
  275.             "bgcp:cb": "background-clip:content-box;",
  276.             "bgcp:nc": "background-clip:no-clip;",
  277.             "bgo": "background-origin:|;",
  278.             "bgo:pb": "background-origin:padding-box;",
  279.             "bgo:bb": "background-origin:border-box;",
  280.             "bgo:cb": "background-origin:content-box;",
  281.             "bgz": "background-size:|;",
  282.             "bgz:a": "background-size:auto;",
  283.             "bgz:ct": "background-size:contain;",
  284.             "bgz:cv": "background-size:cover;",
  285.             "c": "color:#000;",
  286.             "tbl": "table-layout:|;",
  287.             "tbl:a": "table-layout:auto;",
  288.             "tbl:f": "table-layout:fixed;",
  289.             "cps": "caption-side:|;",
  290.             "cps:t": "caption-side:top;",
  291.             "cps:b": "caption-side:bottom;",
  292.             "ec": "empty-cells:|;",
  293.             "ec:s": "empty-cells:show;",
  294.             "ec:h": "empty-cells:hide;",
  295.             "lis": "list-style:|;",
  296.             "lis:n": "list-style:none;",
  297.             "lisp": "list-style-position:|;",
  298.             "lisp:i": "list-style-position:inside;",
  299.             "lisp:o": "list-style-position:outside;",
  300.             "list": "list-style-type:|;",
  301.             "list:n": "list-style-type:none;",
  302.             "list:d": "list-style-type:disc;",
  303.             "list:c": "list-style-type:circle;",
  304.             "list:s": "list-style-type:square;",
  305.             "list:dc": "list-style-type:decimal;",
  306.             "list:dclz": "list-style-type:decimal-leading-zero;",
  307.             "list:lr": "list-style-type:lower-roman;",
  308.             "list:ur": "list-style-type:upper-roman;",
  309.             "lisi": "list-style-image:|;",
  310.             "lisi:n": "list-style-image:none;",
  311.             "q": "quotes:|;",
  312.             "q:n": "quotes:none;",
  313.             "q:ru": "quotes:'\00AB' '\00BB' '\201E' '\201C';",
  314.             "q:en": "quotes:'\201C' '\201D' '\2018' '\2019';",
  315.             "ct": "content:|;",
  316.             "ct:n": "content:normal;",
  317.             "ct:oq": "content:open-quote;",
  318.             "ct:noq": "content:no-open-quote;",
  319.             "ct:cq": "content:close-quote;",
  320.             "ct:ncq": "content:no-close-quote;",
  321.             "ct:a": "content:attr(|);",
  322.             "ct:c": "content:counter(|);",
  323.             "ct:cs": "content:counters(|);",
  324.             "coi": "counter-increment:|;",
  325.             "cor": "counter-reset:|;",
  326.             "va": "vertical-align:|;",
  327.             "va:sup": "vertical-align:super;",
  328.             "va:t": "vertical-align:top;",
  329.             "va:tt": "vertical-align:text-top;",
  330.             "va:m": "vertical-align:middle;",
  331.             "va:bl": "vertical-align:baseline;",
  332.             "va:b": "vertical-align:bottom;",
  333.             "va:tb": "vertical-align:text-bottom;",
  334.             "va:sub": "vertical-align:sub;",
  335.             "ta": "text-align:|;",
  336.             "ta:l": "text-align:left;",
  337.             "ta:c": "text-align:center;",
  338.             "ta:r": "text-align:right;",
  339.             "tal": "text-align-last:|;",
  340.             "tal:a": "text-align-last:auto;",
  341.             "tal:l": "text-align-last:left;",
  342.             "tal:c": "text-align-last:center;",
  343.             "tal:r": "text-align-last:right;",
  344.             "td": "text-decoration:|;",
  345.             "td:n": "text-decoration:none;",
  346.             "td:u": "text-decoration:underline;",
  347.             "td:o": "text-decoration:overline;",
  348.             "td:l": "text-decoration:line-through;",
  349.             "te": "text-emphasis:|;",
  350.             "te:n": "text-emphasis:none;",
  351.             "te:ac": "text-emphasis:accent;",
  352.             "te:dt": "text-emphasis:dot;",
  353.             "te:c": "text-emphasis:circle;",
  354.             "te:ds": "text-emphasis:disc;",
  355.             "te:b": "text-emphasis:before;",
  356.             "te:a": "text-emphasis:after;",
  357.             "th": "text-height:|;",
  358.             "th:a": "text-height:auto;",
  359.             "th:f": "text-height:font-size;",
  360.             "th:t": "text-height:text-size;",
  361.             "th:m": "text-height:max-size;",
  362.             "ti": "text-indent:|;",
  363.             "ti:-": "text-indent:-9999px;",
  364.             "tj": "text-justify:|;",
  365.             "tj:a": "text-justify:auto;",
  366.             "tj:iw": "text-justify:inter-word;",
  367.             "tj:ii": "text-justify:inter-ideograph;",
  368.             "tj:ic": "text-justify:inter-cluster;",
  369.             "tj:d": "text-justify:distribute;",
  370.             "tj:k": "text-justify:kashida;",
  371.             "tj:t": "text-justify:tibetan;",
  372.             "to": "text-outline:|;",
  373.             "to+": "text-outline:0 0 #000;",
  374.             "to:n": "text-outline:none;",
  375.             "tr": "text-replace:|;",
  376.             "tr:n": "text-replace:none;",
  377.             "tt": "text-transform:|;",
  378.             "tt:n": "text-transform:none;",
  379.             "tt:c": "text-transform:capitalize;",
  380.             "tt:u": "text-transform:uppercase;",
  381.             "tt:l": "text-transform:lowercase;",
  382.             "tw": "text-wrap:|;",
  383.             "tw:n": "text-wrap:normal;",
  384.             "tw:no": "text-wrap:none;",
  385.             "tw:u": "text-wrap:unrestricted;",
  386.             "tw:s": "text-wrap:suppress;",
  387.             "tsh": "text-shadow:|;",
  388.             "tsh+": "text-shadow:0 0 0 #000;",
  389.             "tsh:n": "text-shadow:none;",
  390.             "lh": "line-height:|;",
  391.             "whs": "white-space:|;",
  392.             "whs:n": "white-space:normal;",
  393.             "whs:p": "white-space:pre;",
  394.             "whs:nw": "white-space:nowrap;",
  395.             "whs:pw": "white-space:pre-wrap;",
  396.             "whs:pl": "white-space:pre-line;",
  397.             "whsc": "white-space-collapse:|;",
  398.             "whsc:n": "white-space-collapse:normal;",
  399.             "whsc:k": "white-space-collapse:keep-all;",
  400.             "whsc:l": "white-space-collapse:loose;",
  401.             "whsc:bs": "white-space-collapse:break-strict;",
  402.             "whsc:ba": "white-space-collapse:break-all;",
  403.             "wob": "word-break:|;",
  404.             "wob:n": "word-break:normal;",
  405.             "wob:k": "word-break:keep-all;",
  406.             "wob:l": "word-break:loose;",
  407.             "wob:bs": "word-break:break-strict;",
  408.             "wob:ba": "word-break:break-all;",
  409.             "wos": "word-spacing:|;",
  410.             "wow": "word-wrap:|;",
  411.             "wow:nm": "word-wrap:normal;",
  412.             "wow:n": "word-wrap:none;",
  413.             "wow:u": "word-wrap:unrestricted;",
  414.             "wow:s": "word-wrap:suppress;",
  415.             "lts": "letter-spacing:|;",
  416.             "f": "font:|;",
  417.             "f+": "font:1em Arial,sans-serif;",
  418.             "fw": "font-weight:|;",
  419.             "fw:n": "font-weight:normal;",
  420.             "fw:b": "font-weight:bold;",
  421.             "fw:br": "font-weight:bolder;",
  422.             "fw:lr": "font-weight:lighter;",
  423.             "fs": "font-style:|;",
  424.             "fs:n": "font-style:normal;",
  425.             "fs:i": "font-style:italic;",
  426.             "fs:o": "font-style:oblique;",
  427.             "fv": "font-variant:|;",
  428.             "fv:n": "font-variant:normal;",
  429.             "fv:sc": "font-variant:small-caps;",
  430.             "fz": "font-size:|;",
  431.             "fza": "font-size-adjust:|;",
  432.             "fza:n": "font-size-adjust:none;",
  433.             "ff": "font-family:|;",
  434.             "ff:s": "font-family:serif;",
  435.             "ff:ss": "font-family:sans-serif;",
  436.             "ff:c": "font-family:cursive;",
  437.             "ff:f": "font-family:fantasy;",
  438.             "ff:m": "font-family:monospace;",
  439.             "fef": "font-effect:|;",
  440.             "fef:n": "font-effect:none;",
  441.             "fef:eg": "font-effect:engrave;",
  442.             "fef:eb": "font-effect:emboss;",
  443.             "fef:o": "font-effect:outline;",
  444.             "fem": "font-emphasize:|;",
  445.             "femp": "font-emphasize-position:|;",
  446.             "femp:b": "font-emphasize-position:before;",
  447.             "femp:a": "font-emphasize-position:after;",
  448.             "fems": "font-emphasize-style:|;",
  449.             "fems:n": "font-emphasize-style:none;",
  450.             "fems:ac": "font-emphasize-style:accent;",
  451.             "fems:dt": "font-emphasize-style:dot;",
  452.             "fems:c": "font-emphasize-style:circle;",
  453.             "fems:ds": "font-emphasize-style:disc;",
  454.             "fsm": "font-smooth:|;",
  455.             "fsm:a": "font-smooth:auto;",
  456.             "fsm:n": "font-smooth:never;",
  457.             "fsm:aw": "font-smooth:always;",
  458.             "fst": "font-stretch:|;",
  459.             "fst:n": "font-stretch:normal;",
  460.             "fst:uc": "font-stretch:ultra-condensed;",
  461.             "fst:ec": "font-stretch:extra-condensed;",
  462.             "fst:c": "font-stretch:condensed;",
  463.             "fst:sc": "font-stretch:semi-condensed;",
  464.             "fst:se": "font-stretch:semi-expanded;",
  465.             "fst:e": "font-stretch:expanded;",
  466.             "fst:ee": "font-stretch:extra-expanded;",
  467.             "fst:ue": "font-stretch:ultra-expanded;",
  468.             "op": "opacity:|;",
  469.             "op:ie": "filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=100);",
  470.             "op:ms": "-ms-filter:'progid:DXImageTransform.Microsoft.Alpha(Opacity=100)';",
  471.             "rz": "resize:|;",
  472.             "rz:n": "resize:none;",
  473.             "rz:b": "resize:both;",
  474.             "rz:h": "resize:horizontal;",
  475.             "rz:v": "resize:vertical;",
  476.             "cur": "cursor:|;",
  477.             "cur:a": "cursor:auto;",
  478.             "cur:d": "cursor:default;",
  479.             "cur:c": "cursor:crosshair;",
  480.             "cur:ha": "cursor:hand;",
  481.             "cur:he": "cursor:help;",
  482.             "cur:m": "cursor:move;",
  483.             "cur:p": "cursor:pointer;",
  484.             "cur:t": "cursor:text;",
  485.             "pgbb": "page-break-before:|;",
  486.             "pgbb:au": "page-break-before:auto;",
  487.             "pgbb:al": "page-break-before:always;",
  488.             "pgbb:l": "page-break-before:left;",
  489.             "pgbb:r": "page-break-before:right;",
  490.             "pgbi": "page-break-inside:|;",
  491.             "pgbi:au": "page-break-inside:auto;",
  492.             "pgbi:av": "page-break-inside:avoid;",
  493.             "pgba": "page-break-after:|;",
  494.             "pgba:au": "page-break-after:auto;",
  495.             "pgba:al": "page-break-after:always;",
  496.             "pgba:l": "page-break-after:left;",
  497.             "pgba:r": "page-break-after:right;",
  498.             "orp": "orphans:|;",
  499.             "wid": "widows:|;"
  500.         }
  501.     },
  502.     
  503.     'html': {
  504.         'filters': 'html',
  505.         'snippets': {
  506.             'cc:ie6': '<!--[if lte IE 6]>\n\t${child}|\n<![endif]-->',
  507.             'cc:ie': '<!--[if IE]>\n\t${child}|\n<![endif]-->',
  508.             'cc:noie': '<!--[if !IE]><!-->\n\t${child}|\n<!--<![endif]-->',
  509.             'html:4t': '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">\n' +
  510.                     '<html lang="${lang}">\n' +
  511.                     '<head>\n' +
  512.                     '    <meta http-equiv="Content-Type" content="text/html;charset=${charset}">\n' +
  513.                     '    <title></title>\n' +
  514.                     '</head>\n' +
  515.                     '<body>\n\t${child}|\n</body>\n' +
  516.                     '</html>',
  517.             
  518.             'html:4s': '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">\n' +
  519.                     '<html lang="${lang}">\n' +
  520.                     '<head>\n' +
  521.                     '    <meta http-equiv="Content-Type" content="text/html;charset=${charset}">\n' +
  522.                     '    <title></title>\n' +
  523.                     '</head>\n' +
  524.                     '<body>\n\t${child}|\n</body>\n' +
  525.                     '</html>',
  526.             
  527.             'html:xt': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n' +
  528.                     '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="${lang}">\n' +
  529.                     '<head>\n' +
  530.                     '    <meta http-equiv="Content-Type" content="text/html;charset=${charset}" />\n' +
  531.                     '    <title></title>\n' +
  532.                     '</head>\n' +
  533.                     '<body>\n\t${child}|\n</body>\n' +
  534.                     '</html>',
  535.             
  536.             'html:xs': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n' +
  537.                     '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="${lang}">\n' +
  538.                     '<head>\n' +
  539.                     '    <meta http-equiv="Content-Type" content="text/html;charset=${charset}" />\n' +
  540.                     '    <title></title>\n' +
  541.                     '</head>\n' +
  542.                     '<body>\n\t${child}|\n</body>\n' +
  543.                     '</html>',
  544.             
  545.             'html:xxs': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n' +
  546.                     '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="${lang}">\n' +
  547.                     '<head>\n' +
  548.                     '    <meta http-equiv="Content-Type" content="text/html;charset=${charset}" />\n' +
  549.                     '    <title></title>\n' +
  550.                     '</head>\n' +
  551.                     '<body>\n\t${child}|\n</body>\n' +
  552.                     '</html>',
  553.             
  554.             'html:5': '<!DOCTYPE HTML>\n' +
  555.                     '<html lang="${locale}">\n' +
  556.                     '<head>\n' +
  557.                     '    <meta charset="${charset}">\n' +
  558.                     '    <title></title>\n' +
  559.                     '</head>\n' +
  560.                     '<body>\n\t${child}|\n</body>\n' +
  561.                     '</html>'
  562.         },
  563.         
  564.         'abbreviations': {
  565.             'a': '<a href="">',
  566.             'a:link': '<a href="http://|">',
  567.             'a:mail': '<a href="mailto:|">',
  568.             'abbr': '<abbr title="">',
  569.             'acronym': '<acronym title="">',
  570.             'base': '<base href="" />',
  571.             'bdo': '<bdo dir="">',
  572.             'bdo:r': '<bdo dir="rtl">',
  573.             'bdo:l': '<bdo dir="ltr">',
  574.             'link:css': '<link rel="stylesheet" type="text/css" href="${1:style}.css" media="all" />',
  575.             'link:print': '<link rel="stylesheet" type="text/css" href="|print.css" media="print" />',
  576.             'link:favicon': '<link rel="shortcut icon" type="image/x-icon" href="|favicon.ico" />',
  577.             'link:touch': '<link rel="apple-touch-icon" href="|favicon.png" />',
  578.             'link:rss': '<link rel="alternate" type="application/rss+xml" title="RSS" href="|rss.xml" />',
  579.             'link:atom': '<link rel="alternate" type="application/atom+xml" title="Atom" href="atom.xml" />',
  580.             'meta:utf': '<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />',
  581.             'meta:win': '<meta http-equiv="Content-Type" content="text/html;charset=windows-1251" />',
  582.             'meta:compat': '<meta http-equiv="X-UA-Compatible" content="IE=7" />',
  583.             'style': '<style type="text/css">',
  584.             'script': '<script type="text/javascript">',
  585.             'script:src': '<script type="text/javascript" src="">',
  586.             'img': '<img src="" alt="" />',
  587.             'iframe': '<iframe src="" frameborder="0">',
  588.             'embed': '<embed src="" type="" />',
  589.             'object': '<object data="" type="">',
  590.             'param': '<param name="" value="" />',
  591.             'map': '<map name="">',
  592.             'area': '<area shape="" coords="" href="" alt="" />',
  593.             'area:d': '<area shape="default" href="" alt="" />',
  594.             'area:c': '<area shape="circle" coords="" href="" alt="" />',
  595.             'area:r': '<area shape="rect" coords="" href="" alt="" />',
  596.             'area:p': '<area shape="poly" coords="" href="" alt="" />',
  597.             'link': '<link rel="stylesheet" href="" />',
  598.             'form': '<form action="">',
  599.             'form:get': '<form action="" method="get">',
  600.             'form:post': '<form action="" method="post">',
  601.             'label': '<label for="">',
  602.             'input': '<input type="" />',
  603.             'input:hidden': '<input type="hidden" name="" />',
  604.             'input:h': '<input type="hidden" name="" />',
  605.             'input:text': '<input type="text" name="" id="" />',
  606.             'input:t': '<input type="text" name="" id="" />',
  607.             'input:search': '<input type="search" name="" id="" />',
  608.             'input:email': '<input type="email" name="" id="" />',
  609.             'input:url': '<input type="url" name="" id="" />',
  610.             'input:password': '<input type="password" name="" id="" />',
  611.             'input:p': '<input type="password" name="" id="" />',
  612.             'input:datetime': '<input type="datetime" name="" id="" />',
  613.             'input:date': '<input type="date" name="" id="" />',
  614.             'input:datetime-local': '<input type="datetime-local" name="" id="" />',
  615.             'input:month': '<input type="month" name="" id="" />',
  616.             'input:week': '<input type="week" name="" id="" />',
  617.             'input:time': '<input type="time" name="" id="" />',
  618.             'input:number': '<input type="number" name="" id="" />',
  619.             'input:color': '<input type="color" name="" id="" />',
  620.             'input:checkbox': '<input type="checkbox" name="" id="" />',
  621.             'input:c': '<input type="checkbox" name="" id="" />',
  622.             'input:radio': '<input type="radio" name="" id="" />',
  623.             'input:r': '<input type="radio" name="" id="" />',
  624.             'input:range': '<input type="range" name="" id="" />',
  625.             'input:file': '<input type="file" name="" id="" />',
  626.             'input:f': '<input type="file" name="" id="" />',
  627.             'input:submit': '<input type="submit" value="" />',
  628.             'input:s': '<input type="submit" value="" />',
  629.             'input:image': '<input type="image" src="" alt="" />',
  630.             'input:i': '<input type="image" src="" alt="" />',
  631.             'input:reset': '<input type="reset" value="" />',
  632.             'input:button': '<input type="button" value="" />',
  633.             'input:b': '<input type="button" value="" />',
  634.             'select': '<select name="" id=""></select>',
  635.             'option': '<option value=""></option>',
  636.             'textarea': '<textarea name="" id="" cols="30" rows="10">',
  637.             'menu:context': '<menu type="context">',
  638.             'menu:c': '<menu type="context">',
  639.             'menu:toolbar': '<menu type="toolbar">',
  640.             'menu:t': '<menu type="toolbar">',
  641.             'video': '<video src="">',
  642.             'audio': '<audio src="">',
  643.             'html:xml': '<html xmlns="http://www.w3.org/1999/xhtml">',
  644.             'bq': '<blockquote>',
  645.             'acr': '<acronym>',
  646.             'fig': '<figure>',
  647.             'ifr': '<iframe>',
  648.             'emb': '<embed>',
  649.             'obj': '<object>',
  650.             'src': '<source>',
  651.             'cap': '<caption>',
  652.             'colg': '<colgroup>',
  653.             'fst': '<fieldset>',
  654.             'btn': '<button>',
  655.             'optg': '<optgroup>',
  656.             'opt': '<option>',
  657.             'tarea': '<textarea>',
  658.             'leg': '<legend>',
  659.             'sect': '<section>',
  660.             'art': '<article>',
  661.             'hdr': '<header>',
  662.             'ftr': '<footer>',
  663.             'adr': '<address>',
  664.             'dlg': '<dialog>',
  665.             'str': '<strong>',
  666.             'prog': '<progress>',
  667.             'fset': '<fieldset>',
  668.             'datag': '<datagrid>',
  669.             'datal': '<datalist>',
  670.             'kg': '<keygen>',
  671.             'out': '<output>',
  672.             'det': '<details>',
  673.             'cmd': '<command>',
  674.             
  675.             // expandos
  676.             'ol+': 'ol>li',
  677.             'ul+': 'ul>li',
  678.             'dl+': 'dl>dt+dd',
  679.             'map+': 'map>area',
  680.             'table+': 'table>tr>td',
  681.             'colgroup+': 'colgroup>col',
  682.             'colg+': 'colgroup>col',
  683.             'tr+': 'tr>td',
  684.             'select+': 'select>option',
  685.             'optgroup+': 'optgroup>option',
  686.             'optg+': 'optgroup>option'
  687.  
  688.         },
  689.         
  690.         'element_types': {
  691.             'empty': 'area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed,keygen,command',
  692.             'block_level': 'address,applet,blockquote,button,center,dd,del,dir,div,dl,dt,fieldset,form,frameset,hr,iframe,ins,isindex,li,link,map,menu,noframes,noscript,object,ol,p,pre,script,table,tbody,td,tfoot,th,thead,tr,ul,h1,h2,h3,h4,h5,h6',
  693.             'inline_level': 'a,abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,code,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,select,small,span,strike,strong,sub,sup,textarea,tt,u,var'
  694.         }
  695.     },
  696.     
  697.     'xml': {
  698.         'extends': 'html',
  699.         'filters': 'html'
  700.     },
  701.     
  702.     'xsl': {
  703.         'extends': 'html',
  704.         'filters': 'html, xsl',
  705.         'abbreviations': {
  706.             'tm': '<xsl:template match="" mode="">',
  707.             'tmatch': 'tm',
  708.             'tn': '<xsl:template name="">',
  709.             'tname': 'tn',
  710.             'xsl:when': '<xsl:when test="">',
  711.             'wh': 'xsl:when',
  712.             'var': '<xsl:variable name="">',
  713.             'vare': '<xsl:variable name="" select=""/>',
  714.             'if': '<xsl:if test="">',
  715.             'call': '<xsl:call-template name=""/>',
  716.             'attr': '<xsl:attribute name="">',
  717.             'wp': '<xsl:with-param name="" select=""/>',
  718.             'par': '<xsl:param name="" select=""/>',
  719.             'val': '<xsl:value-of select=""/>',
  720.             'co': '<xsl:copy-of select=""/>',
  721.             'each': '<xsl:for-each select="">',
  722.             'for': 'each',
  723.             'ap': '<xsl:apply-templates select="" mode=""/>',
  724.             
  725.             //expandos
  726.             'choose+': 'xsl:choose>xsl:when+xsl:otherwise'
  727.         }
  728.     },
  729.     
  730.     'haml': {
  731.         'filters': 'haml',
  732.         'extends': 'html'
  733.     }
  734. };/**
  735.  * Parsed resources (snippets, abbreviations, variables, etc.) for Zen Coding.
  736.  * Contains convenient method to get access for snippets with respect of 
  737.  * inheritance. Also provides abilitity to store data in different vocabularies
  738.  * ('system' and 'user') for fast and safe resurce update
  739.  * @author Sergey Chikuyonok (serge.che@gmail.com)
  740.  * @link http://chikuyonok.ru
  741.  */
  742. var zen_resources = (function(){
  743.     var TYPE_ABBREVIATION = 'zen-tag',
  744.         TYPE_EXPANDO = 'zen-expando',
  745.     
  746.         /** Reference to another abbreviation or tag */
  747.         TYPE_REFERENCE = 'zen-reference',
  748.         
  749.         VOC_SYSTEM = 'system',
  750.         VOC_USER = 'user',
  751.         
  752.         /** Regular expression for XML tag matching */
  753.         re_tag = /^<(\w+\:?[\w\-]*)((?:\s+[\w\:\-]+\s*=\s*(['"]).*?\3)*)\s*(\/?)>/,
  754.         re_attrs = /([\w\-]+)\s*=\s*(['"])(.*?)\2/g,
  755.         
  756.         system_settings = {},
  757.         user_settings = {};
  758.         
  759.     /**
  760.      * Trim whitespace from string
  761.      * @param {String} text
  762.      * @return {String}
  763.      */
  764.     function trim(text) {
  765.         return (text || "").replace( /^\s+|\s+$/g, "" );
  766.     }
  767.         
  768.     /**
  769.      * Check if specified resource is parsed by Zen Coding
  770.      * @param {Object} obj
  771.      * @return {Boolean}
  772.      */
  773.     function isParsed(obj) {
  774.         return obj && obj.__zen_parsed__;
  775.     }
  776.     
  777.     /**
  778.      * Marks object as parsed by Zen Coding
  779.      * @param {Object}
  780.      */
  781.     function setParsed(obj) {
  782.         obj.__zen_parsed__ = true;
  783.     }
  784.     
  785.     /**
  786.      * Returns resource vocabulary by its name
  787.      * @param {String} name Vocabulary name ('system' or 'user')
  788.      */
  789.     function getVocabulary(name) {
  790.         return name == VOC_SYSTEM ? system_settings : user_settings;
  791.     }
  792.         
  793.     /**
  794.      * Helper function that transforms string into hash
  795.      * @return {Object}
  796.      */
  797.     function stringToHash(str){
  798.         var obj = {}, items = str.split(",");
  799.         for ( var i = 0; i < items.length; i++ )
  800.             obj[ items[i] ] = true;
  801.         return obj;
  802.     }
  803.     
  804.     /**
  805.      * Creates resource inheritance chain for lookups
  806.      * @param {String} vocabulary Resource vocabulary
  807.      * @param {String} syntax Syntax name
  808.      * @param {String} name Resource name
  809.      * @return {Array}
  810.      */
  811.     function createResourceChain(vocabulary, syntax, name) {
  812.         var voc = getVocabulary(vocabulary),
  813.             result = [],
  814.             resource;
  815.         
  816.         if (voc && syntax in voc) {
  817.             resource = voc[syntax];
  818.             if (name in resource)
  819.                 result.push(resource[name]);
  820.         }
  821.         
  822.         // get inheritance definition
  823.         // in case of user-defined vocabulary, resource dependency
  824.         // may be defined in system vocabulary only, so we have to correctly
  825.         // handle this case
  826.         var chain_source;
  827.         if (resource && 'extends' in resource)
  828.             chain_source = resource;
  829.         else if (vocabulary == VOC_USER && syntax in system_settings 
  830.             && 'extends' in system_settings[syntax] )
  831.             chain_source = system_settings[syntax];
  832.             
  833.         if (chain_source) {
  834.             if (!isParsed(chain_source['extends'])) {
  835.                 var ar = chain_source['extends'].split(',');
  836.                 for (var i = 0; i < ar.length; i++) 
  837.                     ar[i] = trim(ar[i]);
  838.                 chain_source['extends'] = ar;
  839.                 setParsed(chain_source['extends']);
  840.             }
  841.             
  842.             // find resource in ancestors
  843.             for (var i = 0; i < chain_source['extends'].length; i++) {
  844.                 var type = chain_source['extends'][i];
  845.                 if (voc[type] && voc[type][name])
  846.                     result.push(voc[type][name]);
  847.             }
  848.         }
  849.         
  850.         return result;
  851.     }
  852.     
  853.     /**
  854.      * Get resource collection from settings vocbulary for specified syntax. 
  855.      * It follows inheritance chain if resource wasn't directly found in
  856.      * syntax settings
  857.      * @param {String} vocabulary Resource vocabulary
  858.      * @param {String} syntax Syntax name
  859.      * @param {String} name Resource name
  860.      */
  861.     function getSubset(vocabulary, syntax, name) {
  862.         var chain = createResourceChain(vocabulary, syntax, name);
  863.         return chain[0];
  864.     }
  865.     
  866.     /**
  867.      * Returns parsed item located in specified vocabulary by its syntax and
  868.      * name
  869.      * @param {String} vocabulary Resource vocabulary
  870.      * @param {String} syntax Syntax name
  871.      * @param {String} name Resource name ('abbreviation', 'snippet')
  872.      * @param {String} item Abbreviation or snippet name
  873.      * @return {Object|null}
  874.      */
  875.     function getParsedItem(vocabulary, syntax, name, item) {
  876.         var chain = createResourceChain(vocabulary, syntax, name),
  877.             result = null,
  878.             res;
  879.             
  880.         for (var i = 0, il = chain.length; i < il; i++) {
  881.             res = chain[i];
  882.             if (item in res) {
  883.                 if (name == 'abbreviations' && !isParsed(res[item])) {
  884.                     // parse abbreviation
  885.                     var value = res[item];
  886.                     res[item] = parseAbbreviation(item, value);
  887.                     res[item].__ref = value;
  888.                     setParsed(res[item]);
  889.                 }
  890.                 
  891.                 result = res[item];
  892.                 break;
  893.             }
  894.         }
  895.         
  896.         return result;
  897.     }
  898.     
  899.     /**
  900.      * Unified object for parsed data
  901.      */
  902.     function entry(type, key, value) {
  903.         return {
  904.             type: type,
  905.             key: key,
  906.             value: value
  907.         };
  908.     }
  909.     
  910.     /**
  911.      * Make expando from string
  912.      * @param {String} key
  913.      * @param {String} value
  914.      * @return {Object}
  915.      */
  916.     function makeExpando(key, value) {
  917.         return entry(TYPE_EXPANDO, key, value);
  918.     }
  919.     
  920.     /**
  921.      * Make abbreviation from string
  922.      * @param {String} key Abbreviation key
  923.      * @param {String} tag_name Expanded element's tag name
  924.      * @param {String} attrs Expanded element's attributes
  925.      * @param {Boolean} is_empty Is expanded element empty or not
  926.      * @return {Object}
  927.      */
  928.     function makeAbbreviation(key, tag_name, attrs, is_empty) {
  929.         var result = {
  930.             name: tag_name,
  931.             is_empty: !!is_empty
  932.         };
  933.         
  934.         if (attrs) {
  935.             var m;
  936.             result.attributes = [];
  937.             while (m = re_attrs.exec(attrs)) {
  938.                 result.attributes.push({
  939.                     name: m[1],
  940.                     value: m[3]
  941.                 });
  942.             }
  943.         }
  944.         
  945.         return entry(TYPE_ABBREVIATION, key, result);
  946.     }
  947.     
  948.     /**
  949.      * Parses single abbreviation
  950.      * @param {String} key Abbreviation name
  951.      * @param {String} value = Abbreviation value
  952.      * @return {Object}
  953.      */
  954.     function parseAbbreviation(key, value) {
  955.         key = trim(key);
  956.         var m;
  957.         if (key.substr(-1) == '+') {
  958.             // this is expando, leave 'value' as is
  959.             return makeExpando(key, value);
  960.         } else if (m = re_tag.exec(value)) {
  961.             return makeAbbreviation(key, m[1], m[2], m[4] == '/');
  962.         } else {
  963.             // assume it's reference to another abbreviation
  964.             return entry(TYPE_REFERENCE, key, value);
  965.         }
  966.     }
  967.     
  968.     return {
  969.         /**
  970.          * Sets new unparsed data for specified settings vocabulary
  971.          * @param {Object} data
  972.          * @param {String} type Vocabulary type ('system' or 'user')
  973.          */
  974.         setVocabulary: function(data, type) {
  975.             if (type == VOC_SYSTEM)
  976.                 system_settings = data;
  977.             else
  978.                 user_settings = data;
  979.         },
  980.         
  981.         /**
  982.          * Get data from specified vocabulary. Can contain parsed entities
  983.          * @param {String} name Vocabulary type ('system' or 'user')
  984.          * @return {Object}
  985.          */
  986.         getVocabulary: getVocabulary,
  987.         
  988.         /**
  989.          * Returns resource value from data set with respect of inheritance
  990.          * @param {String} syntax Resource syntax (html, css, ...)
  991.          * @param {String} name Resource name ('snippets' or 'abbreviation')
  992.          * @param {String} abbr Abbreviation name
  993.          * @return {Object|null}
  994.          */
  995.         getResource: function(syntax, name, item) {
  996.             return getParsedItem(VOC_USER, syntax, name, item) 
  997.                 || getParsedItem(VOC_SYSTEM, syntax, name, item);
  998.         },
  999.         
  1000.         /**
  1001.          * Returns abbreviation value from data set
  1002.          * @param {String} type Resource type (html, css, ...)
  1003.          * @param {String} name Abbreviation name
  1004.          * @return {Object|null}
  1005.          */
  1006.         getAbbreviation: function(type, name) {
  1007.             return this.getResource(type, 'abbreviations', name) 
  1008.                 || this.getResource(type, 'abbreviations', name.replace(/\-/g, ':'));
  1009.         },
  1010.         
  1011.         /**
  1012.          * Returns snippet value from data set
  1013.          * @param {String} type Resource type (html, css, ...)
  1014.          * @param {String} name Snippet name
  1015.          * @return {Object|null}
  1016.          */
  1017.         getSnippet: function(type, name) {
  1018.             return this.getResource(type, 'snippets', name)
  1019.                 || this.getResource(type, 'snippets', name.replace(/\-/g, ':'));
  1020.         },
  1021.         
  1022.         /**
  1023.          * Returns variable value
  1024.          * @return {String}
  1025.          */
  1026.         getVariable: function(name) {
  1027.             return getSubset(VOC_USER, 'variables', name) 
  1028.                 || getSubset(VOC_SYSTEM, 'variables', name);
  1029.         },
  1030.         
  1031.         /**
  1032.          * Returns resource subset from settings vocabulary
  1033.          * @param {String} syntax Syntax name
  1034.          * @param {String} name Resource name
  1035.          * @return {Object}
  1036.          */
  1037.         getSubset: function(syntax, name) {
  1038.             return getSubset(VOC_USER, syntax, name) 
  1039.                 || getSubset(VOC_SYSTEM, syntax, name);
  1040.         },
  1041.         
  1042.         /**
  1043.          * Check if specified item exists in specified resource collection
  1044.          * (like 'empty', 'block_level')
  1045.          * @param {String} syntax 
  1046.          * @param {String} collection Collection name
  1047.          * @param {String} item Item name
  1048.          */
  1049.         isItemInCollection: function(syntax, collection, item) {
  1050.             return item in this.getElementsCollection(getVocabulary(VOC_USER)[syntax], collection)
  1051.                 || item in this.getElementsCollection(getVocabulary(VOC_SYSTEM)[syntax], collection);
  1052.         },
  1053.         
  1054.         /**
  1055.          * Returns specified elements collection (like 'empty', 'block_level') from
  1056.          * <code>resource</code>. If collections wasn't found, returns empty object
  1057.          * @param {Object} resource
  1058.          * @param {String} type
  1059.          * @return {Object}
  1060.          */
  1061.         getElementsCollection: function(resource, type) {
  1062.             if (resource && resource.element_types) {
  1063.                 // if it's not parsed yet ΓÇô do it
  1064.                 var res = resource.element_types;
  1065.                 if (!isParsed(res)) {
  1066.                     for (var p in res) 
  1067.                         res[p] = stringToHash(res[p]);
  1068.                         
  1069.                     setParsed(res);
  1070.                 }
  1071.                 return res[type] || {}
  1072.             }
  1073.             else
  1074.                 return {};
  1075.         },
  1076.         
  1077.         /**
  1078.          * Check if there are resources for specified syntax
  1079.          * @param {String} syntax
  1080.          * @return {Boolean}
  1081.          */
  1082.         hasSyntax: function(syntax) {
  1083.             return syntax in getVocabulary(VOC_USER) 
  1084.                 || syntax in getVocabulary(VOC_SYSTEM);
  1085.         }
  1086.     }
  1087. })();
  1088.  
  1089. try {
  1090.     zen_resources.setVocabulary(zen_settings, 'system');
  1091.     zen_resources.setVocabulary(my_zen_settings, 'user');
  1092. } catch(e) {}/**
  1093.  * Class that parses abbreviation into tree with respect of groups, attributes
  1094.  * and text nodes
  1095.  * @author Sergey Chikuyonok (serge.che@gmail.com)
  1096.  * @link http://chikuyonok.ru
  1097.  * 
  1098.  * @include "zen_coding.js"
  1099.  */var zen_parser = (function(){
  1100.     
  1101.     var re_valid_name = /^[\w\d\-_\$\:@!]+\+?$/i;
  1102.     
  1103.     /**
  1104.      * @class
  1105.      */
  1106.     function TreeNode(parent) {
  1107.         this.abbreviation = '';
  1108.         /** @type {TreeNode} */
  1109.         this.parent = null;
  1110.         this.children = [];
  1111.         this.count = 1;
  1112.         this.name = null;
  1113.         this.text = null;
  1114.         this.attributes = [];
  1115.         this.is_repeating = false;
  1116.         this.has_implict_name = false;
  1117.     }
  1118.     
  1119.     TreeNode.prototype = {
  1120.         /**
  1121.          * Adds passed or creates new child
  1122.          * @param {TreeNode} [child]
  1123.          * @return {TreeNode}
  1124.          */
  1125.         addChild: function(child) {
  1126.             child = child || new TreeNode;
  1127.             child.parent = this;
  1128.             this.children.push(child);
  1129.             return child;
  1130.         },
  1131.         
  1132.         /**
  1133.          * Replace current node in parent's child list with another node
  1134.          * @param {TreeNode} node
  1135.          */
  1136.         replace: function(node) {
  1137.             if (this.parent) {
  1138.                 var children = this.parent.children;
  1139.                 for (var i = 0, il = children.length; i < il; i++) {
  1140.                     if (children[i] === this) {
  1141.                         children[i] = node;
  1142.                         this.parent = null;
  1143.                         return;
  1144.                     }
  1145.                 }
  1146.             }
  1147.         },
  1148.         
  1149.         /**
  1150.          * Sets abbreviation that belongs to current node
  1151.          * @param {String} abbr
  1152.          */
  1153.         setAbbreviation: function(abbr) {
  1154.             this.abbreviation = abbr;
  1155.             var m = abbr.match(/\*(\d+)?$/);
  1156.             if (m) {
  1157.                 this.count = parseInt(m[1] || 1, 10);
  1158.                 this.is_repeating = !m[1];
  1159.                 abbr = abbr.substr(0, abbr.length - m[0].length);
  1160.             }
  1161.             
  1162.             if (abbr) {
  1163.                 var name_text = splitExpression(abbr);
  1164.                 var name = name_text[0];
  1165.                 if (name_text.length == 2)
  1166.                     this.text = name_text[1];
  1167.                     
  1168.                 if (name) {
  1169.                     var attr_result = parseAttributes(name);
  1170.                     this.name = attr_result[0] || 'div';
  1171.                     this.has_implict_name = !attr_result[0];
  1172.                     this.attributes = attr_result[1];
  1173.                 }
  1174.             }
  1175.             
  1176.             // validate name
  1177.             if (this.name && !re_valid_name.test(this.name)) {
  1178.                 throw new Error('InvalidAbbreviation');
  1179.             }
  1180.         },
  1181.         
  1182.         /**
  1183.          * @return {String}
  1184.          */
  1185.         getAbbreviation: function() {
  1186.             return this.expr;
  1187.         },
  1188.         
  1189.         /**
  1190.          * Dump current tree node into a foramtted string
  1191.          * @return {String}
  1192.          */
  1193.         toString: function(level) {
  1194.             level = level || 0;
  1195.             var output = '(empty)';
  1196.             if (this.abbreviation) {
  1197.                 output = '';
  1198.                 if (this.name)
  1199.                     output = this.name;
  1200.                     
  1201.                 if (this.text !== null)
  1202.                     output += (output ? ' ' : '') + '{text: "' + this.text + '"}';
  1203.                     
  1204.                 if (this.attributes.length) {
  1205.                     var attrs = [];
  1206.                     for (var i = 0, il = this.attributes.length; i < il; i++) {
  1207.                         attrs.push(this.attributes[i].name + '="' + this.attributes[i].value + '"'); 
  1208.                     }
  1209.                     output += ' [' + attrs.join(', ') + ']';
  1210.                 }
  1211.             }
  1212.             var result = zen_coding.repeatString('-', level)
  1213.                 + output 
  1214.                 + '\n';
  1215.             for (var i = 0, il = this.children.length; i < il; i++) {
  1216.                 result += this.children[i].toString(level + 1);
  1217.             }
  1218.             
  1219.             return result;
  1220.         },
  1221.         
  1222.         /**
  1223.          * Check if current node contains children with empty <code>expr</code>
  1224.          * property
  1225.          * @return {Boolean}
  1226.          */
  1227.         hasEmptyChildren: function() {
  1228.             for (var i = 0, il = this.children.length; i < il; i++) {
  1229.                 if (this.children[i].isEmpty())
  1230.                     return true;
  1231.             }
  1232.             
  1233.             return false;
  1234.         },
  1235.         
  1236.         /**
  1237.          * @return {Boolean}
  1238.          */
  1239.         isEmpty: function() {
  1240.             return !this.abbreviation;
  1241.         },
  1242.         
  1243.         /**
  1244.          * Check if current node is a text-only node
  1245.          * @return {Boolean}
  1246.          */
  1247.         isTextNode: function() {
  1248.             return !this.name && this.text;
  1249.         }
  1250.     };
  1251.     
  1252.     /**
  1253.      * Check if character is numeric
  1254.      * @requires {Stirng} ch
  1255.      * @return {Boolean}
  1256.      */
  1257.     function isNumeric(ch) {
  1258.         if (typeof(ch) == 'string')
  1259.             ch = ch.charCodeAt(0);
  1260.             
  1261.         return (ch && ch > 47 && ch < 58);
  1262.     }
  1263.     
  1264.     /**
  1265.      * Optimizes tree node: replaces empty nodes with their children
  1266.      * @param {TreeNode} node
  1267.      * @return {TreeNode}
  1268.      */
  1269.     function squash(node) {
  1270.         for (var i = node.children.length - 1; i >=0; i--) {
  1271.             /** @type {TreeNode} */
  1272.             var n = node.children[i];
  1273.             if (n.isEmpty()) {
  1274.                 var args = [i, 1];
  1275.                 for (var j = 0, jl = n.children.length; j < jl; j++) {
  1276.                     args.push(n.children[j]);
  1277.                 }
  1278.                 
  1279.                 Array.prototype.splice.apply(node.children, args);
  1280.             }
  1281.         }
  1282.         
  1283.         return node;
  1284.     }
  1285.     
  1286.     /**
  1287.      * Trim whitespace from string
  1288.      * @param {String} text
  1289.      * @return {String}
  1290.      */
  1291.     function trim(text) {
  1292.         return (text || "").replace( /^\s+|\s+$/g, "" );
  1293.     }
  1294.     
  1295.     /**
  1296.      * Get word, starting at <code>ix</code> character of <code>str</code>
  1297.      */
  1298.     function getWord(ix, str) {
  1299.         var m = str.substring(ix).match(/^[\w\-:\$]+/);
  1300.         return m ? m[0] : '';
  1301.     }
  1302.     
  1303.     /**
  1304.      * Extract attributes and their values from attribute set 
  1305.      * @param {String} attr_set
  1306.      */
  1307.     function extractAttributes(attr_set) {
  1308.         attr_set = trim(attr_set);
  1309.         var loop_count = 100, // endless loop protection
  1310.             re_string = /^(["'])((?:(?!\1)[^\\]|\\.)*)\1/,
  1311.             result = [],
  1312.             attr;
  1313.             
  1314.         while (attr_set && loop_count--) {
  1315.             var attr_name = getWord(0, attr_set);
  1316.             attr = null;
  1317.             if (attr_name) {
  1318.                 attr = {name: attr_name, value: ''};
  1319. //                result[attr_name] = '';
  1320.                 // let's see if attribute has value
  1321.                 var ch = attr_set.charAt(attr_name.length);
  1322.                 switch (ch) {
  1323.                     case '=':
  1324.                         var ch2 = attr_set.charAt(attr_name.length + 1);
  1325.                         if (ch2 == '"' || ch2 == "'") {
  1326.                             // we have a quoted string
  1327.                             var m = attr_set.substring(attr_name.length + 1).match(re_string);
  1328.                             if (m) {
  1329.                                 attr.value = m[2];
  1330.                                 attr_set = trim(attr_set.substring(attr_name.length + m[0].length + 1));
  1331.                             } else {
  1332.                                 // something wrong, break loop
  1333.                                 attr_set = '';
  1334.                             }
  1335.                         } else {
  1336.                             // unquoted string
  1337.                             var m = attr_set.substring(attr_name.length + 1).match(/(.+?)(\s|$)/);
  1338.                             if (m) {
  1339.                                 attr.value = m[1];
  1340.                                 attr_set = trim(attr_set.substring(attr_name.length + m[1].length + 1));
  1341.                             } else {
  1342.                                 // something wrong, break loop
  1343.                                 attr_set = '';
  1344.                             }
  1345.                         }
  1346.                         break;
  1347.                     default:
  1348.                         attr_set = trim(attr_set.substring(attr_name.length));
  1349.                         break;
  1350.                 }
  1351.             } else {
  1352.                 // something wrong, can't extract attribute name
  1353.                 break;
  1354.             }
  1355.             
  1356.             if (attr) result.push(attr);
  1357.         }
  1358.         return result;
  1359.     }
  1360.     
  1361.     /**
  1362.      * Parses tag attributes extracted from abbreviation
  1363.      * @param {String} str
  1364.      */
  1365.     function parseAttributes(str) {
  1366.         /*
  1367.          * Example of incoming data:
  1368.          * #header
  1369.          * .some.data
  1370.          * .some.data#header
  1371.          * [attr]
  1372.          * #item[attr=Hello other="World"].class
  1373.          */
  1374.         var result = [],
  1375.             name = '',
  1376.             collect_name = true,
  1377.             class_name,
  1378.             char_map = {'#': 'id', '.': 'class'};
  1379.         
  1380.         // walk char-by-char
  1381.         var i = 0,
  1382.             il = str.length,
  1383.             val;
  1384.             
  1385.         while (i < il) {
  1386.             var ch = str.charAt(i);
  1387.             switch (ch) {
  1388.                 case '#': // id
  1389.                     val = getWord(i, str.substring(1));
  1390.                     result.push({name: char_map[ch], value: val});
  1391.                     i += val.length + 1;
  1392.                     collect_name = false;
  1393.                     break;
  1394.                 case '.': // class
  1395.                     val = getWord(i, str.substring(1));
  1396.                     if (!class_name) {
  1397.                         // remember object pointer for value modification
  1398.                         class_name = {name: char_map[ch], value: ''};
  1399.                         result.push(class_name);
  1400.                     }
  1401.                     
  1402.                     class_name.value += ((class_name.value) ? ' ' : '') + val;
  1403.                     i += val.length + 1;
  1404.                     collect_name = false;
  1405.                     break;
  1406.                 case '[': //begin attribute set
  1407.                     // search for end of set
  1408.                     var end_ix = str.indexOf(']', i);
  1409.                     if (end_ix == -1) {
  1410.                         // invalid attribute set, stop searching
  1411.                         i = str.length;
  1412.                     } else {
  1413.                         var attrs = extractAttributes(str.substring(i + 1, end_ix));
  1414.                         for (var j = 0, jl = attrs.length; j < jl; j++) {
  1415.                             result.push(attrs[j]);
  1416.                         }
  1417.                         i = end_ix;
  1418.                     }
  1419.                     collect_name = false;
  1420.                     break;
  1421.                 default:
  1422.                     if (collect_name)
  1423.                         name += ch;
  1424.                     i++;
  1425.             }
  1426.         }
  1427.         
  1428.         return [name, result];
  1429.     }
  1430.     
  1431.     /**
  1432.      * @param {TreeNode} node
  1433.      * @return {TreeNode}
  1434.      */
  1435.     function optimizeTree(node) {
  1436.         while (node.hasEmptyChildren())
  1437.             squash(node);
  1438.             
  1439.         for (var i = 0, il = node.children.length; i < il; i++) {
  1440.             optimizeTree(node.children[i]);
  1441.         }
  1442.         
  1443.         return node;
  1444.     }
  1445.     
  1446.     /**
  1447.      * Split expression by node name and its content, if exists. E.g. if we pass
  1448.      * <code>a{Text}</code> expression, it will be splitted into <code>a</code>
  1449.      * and <code>Text</code>
  1450.      * @param {String} expr
  1451.      * @return {Array} Result with one or two elements (if expression contains
  1452.      * text node)
  1453.      */
  1454.     function splitExpression(expr) {
  1455.         // fast test on text node
  1456.         if (expr.indexOf('{') == -1)
  1457.             return [expr];
  1458.             
  1459.         var attr_lvl = 0,
  1460.             text_lvl = 0,
  1461.             brace_stack = [],
  1462.             i = 0,
  1463.             il = expr.length,
  1464.             ch;
  1465.             
  1466.         while (i < il) {
  1467.             ch = expr.charAt(i);
  1468.             switch (ch) {
  1469.                 case '[':
  1470.                     if (!text_lvl)
  1471.                         attr_lvl++;
  1472.                     break;
  1473.                 case ']':
  1474.                     if (!text_lvl)
  1475.                         attr_lvl--;
  1476.                     break;
  1477.                 case '{':
  1478.                     if (!attr_lvl) {
  1479.                         text_lvl++;
  1480.                         brace_stack.push(i);
  1481.                     }
  1482.                     break;
  1483.                 case '}':
  1484.                     if (!attr_lvl) {
  1485.                         text_lvl--;
  1486.                         var brace_start = brace_stack.pop();
  1487.                         if (text_lvl === 0) {
  1488.                             // found braces bounds
  1489.                             return [
  1490.                                 expr.substring(0, brace_start),
  1491.                                 expr.substring(brace_start + 1, i)
  1492.                             ];
  1493.                         }
  1494.                     }
  1495.                     break;
  1496.             }
  1497.             i++;
  1498.         }
  1499.         
  1500.         // if we are here, then no valid text node found
  1501.         return [expr];
  1502.     }
  1503.     
  1504.     
  1505.     return {
  1506.         /**
  1507.          * Parses abbreviation into tree with respect of groups, 
  1508.          * text nodes and attributes. Each node of the tree is a single 
  1509.          * abbreviation. Tree represents actual structure of the outputted 
  1510.          * result
  1511.          * @param {String} abbr Abbreviation to parse
  1512.          * @return {TreeNode}
  1513.          */
  1514.         parse: function(abbr) {
  1515.             var root = new TreeNode,
  1516.                 context = root.addChild(),
  1517.                 i = 0,
  1518.                 il = abbr.length,
  1519.                 text_lvl = 0,
  1520.                 attr_lvl = 0,
  1521.                 group_lvl = 0,
  1522.                 group_stack = [root],
  1523.                 ch, prev_ch,
  1524.                 token = '';
  1525.                 
  1526.             group_stack.last = function() {
  1527.                 return this[this.length - 1];
  1528.             };
  1529.             
  1530.             var dumpToken = function() {
  1531.                 if (token)
  1532.                     context.setAbbreviation(token);
  1533.                 token = '';
  1534.             };
  1535.                 
  1536.             while (i < il) {
  1537.                 ch = abbr.charAt(i);
  1538.                 prev_ch = i ? abbr.charAt(i - 1) : '';
  1539.                 switch (ch) {
  1540.                     case '{':
  1541.                         if (!attr_lvl)
  1542.                             text_lvl++;
  1543.                         token += ch;
  1544.                         break;
  1545.                     case '}':
  1546.                         if (!attr_lvl)
  1547.                             text_lvl--;
  1548.                         token += ch;
  1549.                         break;
  1550.                     case '[':
  1551.                         if (!text_lvl)
  1552.                             attr_lvl++;
  1553.                         token += ch;
  1554.                         break;
  1555.                     case ']':
  1556.                         if (!text_lvl)
  1557.                             attr_lvl--;
  1558.                         token += ch;
  1559.                         break;
  1560.                     case '(':
  1561.                         if (!text_lvl && !attr_lvl) {
  1562.                             // beginning of the new group
  1563.                             dumpToken();
  1564.                             
  1565.                             if (prev_ch != '+' && prev_ch != '>') {
  1566.                                 // previous char is not an operator, assume it's
  1567.                                 // a sibling
  1568.                                 context = context.parent.addChild();
  1569.                             }
  1570.                             
  1571.                             group_stack.push(context);
  1572.                             context = context.addChild();
  1573.                         } else {
  1574.                             token += ch;
  1575.                         }
  1576.                         break;
  1577.                     case ')':
  1578.                         if (!text_lvl && !attr_lvl) {
  1579.                             // end of the group, pop stack
  1580.                             dumpToken();
  1581.                             context = group_stack.pop();
  1582.                             
  1583.                             if (i < il - 1 && abbr.charAt(i + 1) == '*') {
  1584.                                 // group multiplication
  1585.                                 var group_mul = '', n_ch;
  1586.                                 for (var j = i + 2; j < il; j++) {
  1587.                                     n_ch = abbr.charAt(j);
  1588.                                     if (isNumeric(n_ch))
  1589.                                         group_mul += n_ch;
  1590.                                     else 
  1591.                                         break;
  1592.                                 }
  1593.                                 
  1594.                                 i += group_mul.length + 1;
  1595.                                 group_mul = parseInt(group_mul || 1, 10);
  1596.                                 while (1 < group_mul--)
  1597.                                     context.parent.addChild(context);
  1598. //                                    last_parent.addChild(cur_item);
  1599.                             }
  1600.                             
  1601.                         } else {
  1602.                             token += ch;
  1603.                         }
  1604.                         break;
  1605.                     case '+': // sibling operator
  1606.                         if (!text_lvl && !attr_lvl && i != il - 1 /* expando? */) {
  1607.                             dumpToken();
  1608.                             context = context.parent.addChild();
  1609.                         } else {
  1610.                             token += ch;
  1611.                         }
  1612.                         break;
  1613.                     case '>': // child operator
  1614.                         if (!text_lvl && !attr_lvl) {
  1615.                             dumpToken();
  1616.                             context = context.addChild();
  1617.                         } else {
  1618.                             token += ch;
  1619.                         }
  1620.                         break;
  1621.                     default:
  1622.                         token += ch;
  1623.                 }
  1624.                 
  1625.                 i++;
  1626.             }
  1627.             // put the final token
  1628.             dumpToken();
  1629.             
  1630.             return optimizeTree(root);
  1631.         },
  1632.         
  1633.         TreeNode: TreeNode,
  1634.         optimizeTree: optimizeTree
  1635.     }
  1636. })();/**
  1637.  * Core library that do all Zen Coding magic
  1638.  * @author Sergey Chikuyonok (serge.che@gmail.com)
  1639.  * @link http://chikuyonok.ru
  1640.  * @include "settings.js"
  1641.  * @include "zen_parser.js"
  1642.  * @include "zen_resources.js"
  1643.  */
  1644. var zen_coding = (function(){
  1645.     var re_tag = /<\/?[\w:\-]+(?:\s+[\w\-:]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*\s*(\/?)>$/,
  1646.     
  1647.         caret_placeholder = '{%::zen-caret::%}',
  1648.         newline = '\n',
  1649.         
  1650.         /** List of registered filters */
  1651.         filters = {},
  1652.         /** Filters that will be applied for unknown syntax */
  1653.         basic_filters = 'html',
  1654.         
  1655.         profiles = {},
  1656.         default_profile = {
  1657.             tag_case: 'lower',
  1658.             attr_case: 'lower',
  1659.             attr_quotes: 'double',
  1660.             
  1661.             // each tag on new line
  1662.             tag_nl: 'decide',
  1663.             
  1664.             place_cursor: true,
  1665.             
  1666.             // indent tags
  1667.             indent: true,
  1668.             
  1669.             // how many inline elements should be to force line break 
  1670.             // (set to 0 to disable)
  1671.             inline_break: 3,
  1672.             
  1673.             // use self-closing style for writing empty elements, e.g. <br /> or <br>
  1674.             self_closing_tag: 'xhtml',
  1675.             
  1676.             // Profile-level output filters, re-defines syntax filters 
  1677.             filters: ''
  1678.         };
  1679.     
  1680.     function isNumeric(ch) {
  1681.         if (typeof(ch) == 'string')
  1682.             ch = ch.charCodeAt(0);
  1683.             
  1684.         return (ch && ch > 47 && ch < 58);
  1685.     }
  1686.     
  1687.     /**
  1688.      * ╨ƒ╤Ç╨╛╨▓╨╡╤Ç╤Å╨╡╤é, ╤Å╨▓╨╗╤Å╨╡╤é╤ü╤Å ╨╗╨╕ ╤ü╨╕╨╝╨▓╨╛╨╗ ╨┤╨╛╨┐╤â╤ü╤é╨╕╨╝╤ï╨╝ ╨▓ ╨░╨▒╨▒╤Ç╨╡╨▓╨╕╨░╤é╤â╤Ç╨╡
  1689.      * @param {String} ch
  1690.      * @return {Boolean}
  1691.      */
  1692.     function isAllowedChar(ch) {
  1693.         ch = String(ch); // convert Java object to JS
  1694.         var char_code = ch.charCodeAt(0),
  1695.             special_chars = '#.>+*:$-_!@[]()|';
  1696.         
  1697.         return (char_code > 64 && char_code < 91)       // uppercase letter
  1698.                 || (char_code > 96 && char_code < 123)  // lowercase letter
  1699.                 || isNumeric(ch)                        // number
  1700.                 || special_chars.indexOf(ch) != -1;     // special character
  1701.     }
  1702.     
  1703.     /**
  1704.      * ╨Æ╨╛╨╖╨▓╤Ç╨░╤ë╨░╨╡╤é ╤ü╨╕╨╝╨▓╨╛╨╗ ╨┐╨╡╤Ç╨╡╨▓╨╛╨┤╨░ ╤ü╤é╤Ç╨╛╨║╨╕, ╨╕╤ü╨┐╨╛╨╗╤î╨╖╤â╨╡╨╝╤ï╨╣ ╨▓ ╤Ç╨╡╨┤╨░╨║╤é╨╛╤Ç╨╡
  1705.      * @return {String}
  1706.      */
  1707.     function getNewline() {
  1708.         return zen_coding.getNewline();
  1709.     }
  1710.     
  1711.     /**
  1712.      * Returns caret placeholder
  1713.      * @return {String}
  1714.      */
  1715.     function getCaretPlaceholder() {
  1716.         return (typeof(caret_placeholder) != 'string') 
  1717.             ? caret_placeholder()
  1718.             : caret_placeholder
  1719.     }
  1720.     
  1721.     /**
  1722.      * Split text into lines. Set <code>remove_empty</code> to true to filter
  1723.      * empty lines
  1724.      * @param {String} text
  1725.      * @param {Boolean} [remove_empty]
  1726.      * @return {Array}
  1727.      */
  1728.     function splitByLines(text, remove_empty) {
  1729.         // IE fails to split string by regexp, 
  1730.         // need to normalize newlines first
  1731.         // Also, Mozilla's Rhiho JS engine has a wierd newline bug
  1732.         var nl = getNewline();
  1733.         var lines = (text || '')
  1734.             .replace(/\r\n/g, '\n')
  1735.             .replace(/\n\r/g, '\n')
  1736.             .replace(/\r/g, '\n')
  1737.             .replace(/\n/g, nl)
  1738.             .split(nl);
  1739.         
  1740.         if (remove_empty) {
  1741.             for (var i = lines.length; i >= 0; i--) {
  1742.                 if (!trim(lines[i]))
  1743.                     lines.splice(i, 1);
  1744.             }
  1745.         }
  1746.         
  1747.         return lines;
  1748.     }
  1749.     
  1750.     /**
  1751.      * Trim whitespace from string
  1752.      * @param {String} text
  1753.      * @return {String}
  1754.      */
  1755.     function trim(text) {
  1756.         return (text || "").replace( /^\s+|\s+$/g, "" );
  1757.     }
  1758.     
  1759.     function createProfile(options) {
  1760.         var result = {};
  1761.         for (var p in default_profile)
  1762.             result[p] = (p in options) ? options[p] : default_profile[p];
  1763.         
  1764.         return result;
  1765.     }
  1766.     
  1767.     function setupProfile(name, options) {
  1768.         profiles[name.toLowerCase()] = createProfile(options || {});
  1769.     }
  1770.     
  1771.     /**
  1772.      * Repeats string <code>how_many</code> times
  1773.      * @param {String} str
  1774.      * @param {Number} how_many
  1775.      * @return {String}
  1776.      */
  1777.     function repeatString(str, how_many) {
  1778.         var result = '';
  1779.         for (var i = 0; i < how_many; i++) 
  1780.             result += str;
  1781.             
  1782.         return result;
  1783.     }
  1784.     
  1785.     /**
  1786.      * Indents text with padding
  1787.      * @param {String} text Text to indent
  1788.      * @param {String|Number} pad Padding size (number) or padding itself (string)
  1789.      * @return {String}
  1790.      */
  1791.     function padString(text, pad) {
  1792.         var pad_str = (typeof(pad) == 'number') 
  1793.                 ? repeatString(getIndentation(), pad) 
  1794.                 : pad, 
  1795.             result = '';
  1796.         
  1797.         var lines = splitByLines(text),
  1798.             nl = getNewline();
  1799.             
  1800.         result += lines[0];
  1801.         for (var j = 1; j < lines.length; j++) 
  1802.             result += nl + pad_str + lines[j];
  1803.             
  1804.         return result;
  1805.     }
  1806.     
  1807.     /**
  1808.      * Class inheritance method
  1809.      * @param {Function} derived Derived class
  1810.      * @param {Function} from Base class
  1811.      */
  1812.     function inherit(derived, from) {
  1813.         var Inheritance = function(){};
  1814.     
  1815.         Inheritance.prototype = from.prototype;
  1816.     
  1817.         derived.prototype = new Inheritance();
  1818.         derived.prototype.constructor = derived;
  1819.         derived.baseConstructor = from;
  1820.         derived.superClass = from.prototype;
  1821.     };
  1822.     
  1823.     /**
  1824.      * Check if passed abbreviation is snippet
  1825.      * @param {String} abbr
  1826.      * @param {String} type
  1827.      * @return {Boolean}
  1828.      */
  1829.     function isShippet(abbr, type) {
  1830.         return getSnippet(type, filterNodeName(abbr)) ? true : false;
  1831.     }
  1832.     
  1833.     /**
  1834.      * Test if passed string ends with XHTML tag. This method is used for testing
  1835.      * '>' character: it belongs to tag or it's a part of abbreviation? 
  1836.      * @param {String} str
  1837.      * @return {Boolean}
  1838.      */
  1839.     function isEndsWithTag(str) {
  1840.         return re_tag.test(str);
  1841.     }
  1842.     
  1843.     /**
  1844.      * Replace variables like ${var} in string
  1845.      * @param {String} str
  1846.      * @param {Object|Function} [vars] Variable set (default is <code>zen_settings.variables</code>) 
  1847.      * @return {String}
  1848.      */
  1849.     function replaceVariables(str, vars) {
  1850.         var callback;
  1851.         
  1852.         if (typeof vars == 'function')
  1853.             callback = vars;
  1854.         else if (vars)
  1855.             callback = function(str, p1) {
  1856.                 return (p1 in vars) ? vars[p1] : str;
  1857.             };
  1858.         else 
  1859.             callback = function(str, p1) {
  1860.                 var v = getVariable(p1);
  1861.                 return (v !== null && typeof v != 'undefined') ? v : str;
  1862.             }
  1863.         
  1864.         return str.replace(/\$\{([\w\-]+)\}/g, callback);
  1865.     }
  1866.     
  1867.     /**
  1868.      * Removes any unnecessary characters from node name
  1869.      * @param {String} name
  1870.      * @return {String}
  1871.      */
  1872.     function filterNodeName(name) {
  1873.         return (name || '').replace(/(.+)\!$/, '$1');
  1874.     }
  1875.     
  1876.     /**
  1877.      * Test if text contains output placeholder $#
  1878.      * @param {String} text
  1879.      * @return {Boolean}
  1880.      */
  1881.     function hasOutputPlaceholder(/* String */ text) {
  1882.         for (var i = 0, il = text.length; i < il; i++) {
  1883.             var ch = text.charAt(i);
  1884.             if (ch == '\\') { // escaped char
  1885.                 i++;
  1886.                 continue;
  1887.             } else if (ch == '$' && text.charAt(i + 1) == '#') {
  1888.                 return true;
  1889.             }
  1890.         }
  1891.         
  1892.         return false;
  1893.     }
  1894.     
  1895.     /**
  1896.      * Tag
  1897.      * @class
  1898.      * @param {zen_parser.TreeNode} node Parsed tree node
  1899.      * @param {String} type Tag type (html, xml)
  1900.      */
  1901.     function Tag(node, type) {
  1902.         type = type || 'html';
  1903.         
  1904.         var abbr = null;
  1905.         if (node.name) {
  1906.             abbr = getAbbreviation(type, filterNodeName(node.name));
  1907.             if (abbr && abbr.type == 'zen-reference')
  1908.                 abbr = getAbbreviation(type, filterNodeName(abbr.value));
  1909.         }
  1910.         
  1911.         this.name = (abbr) ? abbr.value.name : node.name;
  1912.         this.real_name = node.name;
  1913.         this.count = node.count || 1;
  1914.         this._abbr = abbr;
  1915.         this.syntax = type;
  1916.         this._content = '';
  1917.         this._paste_content = '';
  1918.         this.repeat_by_lines = node.is_repeating;
  1919.         this.is_repeating = node && node.count > 1;
  1920.         this.parent = null;
  1921.         this.has_implicit_name = node.has_implict_name;
  1922.         
  1923.         this.setContent(node.text);
  1924.         
  1925.         // add default attributes
  1926.         if (this._abbr)
  1927.             this.copyAttributes(this._abbr.value);
  1928.         
  1929.         this.copyAttributes(node);
  1930.     }
  1931.     
  1932.     Tag.prototype = {
  1933.         /**
  1934.          * Adds new child tag to current one
  1935.          * @param {Tag} tag
  1936.          */
  1937.         addChild: function(tag) {
  1938.             if (!this.children)
  1939.                 this.children = [];
  1940.                 
  1941.             tag.parent = this;
  1942.             this.children.push(tag);
  1943.         },
  1944.         
  1945.         /**
  1946.          * Adds new attribute
  1947.          * @param {String} name Attribute's name
  1948.          * @param {String} value Attribute's value
  1949.          */
  1950.         addAttribute: function(name, value) {
  1951.             if (!this.attributes)
  1952.                 this.attributes = [];
  1953.                 
  1954.             if (!this._attr_hash)
  1955.                 this._attr_hash = {};
  1956.             
  1957.             // escape pipe (caret) character with internal placeholder
  1958.             value = replaceUnescapedSymbol(value, '|', getCaretPlaceholder());
  1959.             
  1960.             var a;
  1961.             if (name in this._attr_hash) {
  1962.                 // attribute already exists, decide what to do
  1963.                 a = this._attr_hash[name];
  1964.                 if (name == 'class') {
  1965.                     // 'class' is a magic attribute
  1966.                     a.value += ((a.value) ? ' ' : '') + value;
  1967.                 } else {
  1968.                     a.value = value;
  1969.                 }
  1970.             } else {
  1971.                 a = {name: name, value: value};
  1972.                 this._attr_hash[name] = a
  1973.                 this.attributes.push(a);
  1974.             }
  1975.         },
  1976.         
  1977.         /**
  1978.          * Copy attributes from parsed node
  1979.          */
  1980.         copyAttributes: function(node) {
  1981.             if (node && node.attributes)
  1982.                 for (var i = 0, il = node.attributes.length; i < il; i++) {
  1983.                     var attr = node.attributes[i];
  1984.                     this.addAttribute(attr.name, attr.value);
  1985.                 }
  1986.         },
  1987.         
  1988.         /**
  1989.          * This function tests if current tags' content contains xHTML tags. 
  1990.          * This function is mostly used for output formatting
  1991.          */
  1992.         hasTagsInContent: function() {
  1993.             return this.getContent() && re_tag.test(this.getContent());
  1994.         },
  1995.         
  1996.         /**
  1997.          * Set textual content for tag
  1998.          * @param {String} str Tag's content
  1999.          */
  2000.         setContent: function(str) {
  2001.             this._content = replaceUnescapedSymbol(str || '', '|', getCaretPlaceholder());
  2002.         },
  2003.         
  2004.         /**
  2005.          * Returns tag's textual content
  2006.          * @return {String}
  2007.          */
  2008.         getContent: function() {
  2009.             return this._content || '';
  2010.         },
  2011.         
  2012.         /**
  2013.          * Set content that should be pasted to the output
  2014.          * @param {String} val
  2015.          */
  2016.         setPasteContent: function(val) {
  2017.             this._paste_content = zen_coding.escapeText(val);
  2018.         },
  2019.         
  2020.         /**
  2021.          * Get content that should be pasted to the output
  2022.          * @return {String}
  2023.          */
  2024.         getPasteContent: function() {
  2025.             return this._paste_content;
  2026.         },
  2027.         
  2028.         /**
  2029.          * Search for deepest and latest child of current element
  2030.          * @return {Tag|null} Returns null if there's no children
  2031.          */
  2032.         findDeepestChild: function() {
  2033.             if (!this.children || !this.children.length)
  2034.                 return null;
  2035.                 
  2036.             var deepest_child = this;
  2037.             while (true) {
  2038.                 deepest_child = deepest_child.children[ deepest_child.children.length - 1 ];
  2039.                 if (!deepest_child.children || !deepest_child.children.length)
  2040.                     break;
  2041.             }
  2042.             
  2043.             return deepest_child;
  2044.         }
  2045.     };
  2046.     
  2047.     /**
  2048.      * Snippet
  2049.      * @param {zen_parser.TreeNode} node
  2050.      * @param {String} type Tag type (html, xml)
  2051.      */
  2052.     function Snippet(node, type) {
  2053.         /** @type {String} */
  2054.         this.name = filterNodeName(node.name);
  2055.         this.real_name = node.name;
  2056.         this.count = node.count;
  2057.         this.children = [];
  2058.         this._content = node.text || '';
  2059.         this.repeat_by_lines = node.is_repeating;
  2060.         this.is_repeating = node && node.count > 1;
  2061.         this.attributes = [];
  2062.         this.value = replaceUnescapedSymbol(getSnippet(type, this.name), '|', getCaretPlaceholder());
  2063.         this.parent = null;
  2064.         this.syntax = type;
  2065.         
  2066.         this.addAttribute('id', getCaretPlaceholder());
  2067.         this.addAttribute('class', getCaretPlaceholder());
  2068.         this.copyAttributes(node);
  2069.     }
  2070.     
  2071.     inherit(Snippet, Tag);
  2072.     
  2073.     /**
  2074.      * Returns abbreviation value from data set
  2075.      * @param {String} type Resource type (html, css, ...)
  2076.      * @param {String} abbr Abbreviation name
  2077.      * @return {Object|null}
  2078.      */
  2079.     function getAbbreviation(type, abbr) {
  2080.         return zen_resources.getAbbreviation(type, abbr);
  2081.     }
  2082.     
  2083.     /**
  2084.      * Returns snippet value from data set
  2085.      * @param {String} type Resource type (html, css, ...)
  2086.      * @param {String} snippet_name Snippet name
  2087.      * @return {Object|null}
  2088.      */
  2089.     function getSnippet(type, snippet_name) {
  2090.         return zen_resources.getSnippet(type, snippet_name);
  2091.     }
  2092.     
  2093.     /**
  2094.      * Returns variable value
  2095.      * @return {String}
  2096.      */
  2097.     function getVariable(name) {
  2098.         return zen_resources.getVariable(name);
  2099.     }
  2100.     
  2101.     /**
  2102.      * Returns indentation string
  2103.      * @return {String}
  2104.      */
  2105.     function getIndentation() {
  2106.         return getVariable('indentation');
  2107.     }
  2108.     
  2109.     /**
  2110.      * @class
  2111.      * Creates simplified tag from Zen Coding tag
  2112.      * @param {Tag} tag
  2113.      */
  2114.     function ZenNode(tag) {
  2115.         this.type = (tag instanceof Snippet) ? 'snippet' : 'tag';
  2116.         this.name = tag.name;
  2117.         this.real_name = tag.real_name;
  2118.         this.children = [];
  2119.         this.counter = 1;
  2120.         this.is_repeating = tag.is_repeating;
  2121.         this.repeat_by_lines = tag.repeat_by_lines;
  2122.         this.has_implicit_name = this.type == 'tag' && tag.has_implicit_name;
  2123.         
  2124.         // create deep copy of attribute list so we can change
  2125.         // their values in runtime without affecting other nodes
  2126.         // created from the same tag
  2127.         this.attributes = [];
  2128.         if (tag.attributes) {
  2129.             for (var i = 0, il = tag.attributes.length; i < il; i++) {
  2130.                 var a =  tag.attributes[i];
  2131.                 this.attributes.push({
  2132.                     name: a.name,
  2133.                     value: a.value
  2134.                 });
  2135.             }
  2136.         }
  2137.         
  2138.         /** @type {Tag} Source element from which current tag was created */
  2139.         this.source = tag;
  2140.         
  2141.         // relations
  2142.         /** @type {ZenNode} */
  2143.         this.parent = null;
  2144.         /** @type {ZenNode} */
  2145.         this.nextSibling = null;
  2146.         /** @type {ZenNode} */
  2147.         this.previousSibling = null;
  2148.         
  2149.         // output params
  2150.         this.start = '';
  2151.         this.end = '';
  2152.         this.content = tag.getContent() || '';
  2153.         this.padding = '';
  2154.     }
  2155.     
  2156.     ZenNode.prototype = {
  2157.         /**
  2158.          * @type {ZenNode} tag
  2159.          */
  2160.         addChild: function(tag) {
  2161.             tag.parent = this;
  2162.             
  2163.             // check for implicit name
  2164.             if (tag.has_implicit_name && this.isInline())
  2165.                 tag.name = 'span';
  2166.             
  2167.             var last_child = this.children[this.children.length - 1];
  2168.             if (last_child) {
  2169.                 tag.previousSibling = last_child;
  2170.                 last_child.nextSibling = tag;
  2171.             }
  2172.             
  2173.             this.children.push(tag);
  2174.         },
  2175.         
  2176.         /**
  2177.          * Get attribute's value.
  2178.          * @param {String} name
  2179.          * @return {String|null} Returns <code>null</code> if attribute wasn't found
  2180.          */
  2181.         getAttribute: function(name) {
  2182.             name = name.toLowerCase();
  2183.             for (var i = 0, il = this.attributes.length; i < il; i++) {
  2184.                 if (this.attributes[i].name.toLowerCase() == name)
  2185.                     return this.attributes[i].value;
  2186.             }
  2187.             
  2188.             return null;
  2189.         },
  2190.         
  2191.         /**
  2192.          * Test if current tag is unary (no closing tag)
  2193.          * @return {Boolean}
  2194.          */
  2195.         isUnary: function() {
  2196.             if (this.type == 'snippet')
  2197.                 return false;
  2198.                 
  2199.             return (this.source._abbr && this.source._abbr.value.is_empty) 
  2200.                 || zen_resources.isItemInCollection(this.source.syntax, 'empty', this.name);
  2201.         },
  2202.         
  2203.         /**
  2204.          * Test if current tag is inline-level (like <strong>, <img>)
  2205.          * @return {Boolean}
  2206.          */
  2207.         isInline: function() {
  2208.             return this.type == 'text' 
  2209.                 || zen_resources.isItemInCollection(this.source.syntax, 'inline_level', this.name);
  2210.         },
  2211.         
  2212.         /**
  2213.          * Test if current element is block-level
  2214.          * @return {Boolean}
  2215.          */
  2216.         isBlock: function() {
  2217.             return this.type == 'snippet' || !this.isInline();
  2218.         },
  2219.         
  2220.         /**
  2221.          * This function tests if current tags' content contains xHTML tags. 
  2222.          * This function is mostly used for output formatting
  2223.          */
  2224.         hasTagsInContent: function() {
  2225.             return this.content && re_tag.test(this.content);
  2226.         },
  2227.         
  2228.         /**
  2229.          * Check if tag has child elements
  2230.          * @return {Boolean}
  2231.          */
  2232.         hasChildren: function() {
  2233.             return !!this.children.length;
  2234.         },
  2235.         
  2236.         /**
  2237.          * Test if current tag contains block-level children
  2238.          * @return {Boolean}
  2239.          */
  2240.         hasBlockChildren: function() {
  2241.             if (this.hasTagsInContent() && this.isBlock()) {
  2242.                 return true;
  2243.             }
  2244.             
  2245.             for (var i = 0; i < this.children.length; i++) {
  2246.                 if (this.children[i].isBlock())
  2247.                     return true;
  2248.             }
  2249.             
  2250.             return false;
  2251.         },
  2252.         
  2253.         /**
  2254.          * Search for deepest and latest child of current element
  2255.          * @return {ZenNode|null} Returns <code>null</code> if there's no children
  2256.          */
  2257.         findDeepestChild: function() {
  2258.             if (!this.children.length)
  2259.                 return null;
  2260.                 
  2261.             var deepest_child = this;
  2262.             while (true) {
  2263.                 deepest_child = deepest_child.children[ deepest_child.children.length - 1 ];
  2264.                 if (!deepest_child.children.length)
  2265.                     break;
  2266.             }
  2267.             
  2268.             return deepest_child;
  2269.         },
  2270.         
  2271.         /**
  2272.          * @return {String}
  2273.          */
  2274.         toString: function() {
  2275.             var content = '';
  2276.             for (var i = 0, il = this.children.length; i < il; i++) {
  2277.                 content += this.children[i].toString();
  2278.             }
  2279.             
  2280.             return this.start + this.content + content + this.end;
  2281.         },
  2282.         
  2283.         /**
  2284.          * Test if current element contains output placeholder (aka $#)
  2285.          * @return {Boolean}
  2286.          */
  2287.         hasOutputPlaceholder: function() {
  2288.             if (hasOutputPlaceholder(this.content)) {
  2289.                 return true;
  2290.             } else {
  2291.                 // search inside attributes
  2292.                 for (var i = 0, il = this.attributes.length; i < il; i++) {
  2293.                     if (hasOutputPlaceholder(this.attributes[i].value))
  2294.                         return true;
  2295.                 }
  2296.             }
  2297.             
  2298.             return false;
  2299.         },
  2300.         
  2301.         /**
  2302.          * Recursively search for elements with output placeholders (aka $#)
  2303.          * inside current element (not included in result)
  2304.          * @param {Array} _arr
  2305.          * @return {Array} Array of elements with output placeholders.  
  2306.          */
  2307.         findElementsWithOutputPlaceholder: function(_arr) {
  2308.             _arr = _arr || [];
  2309.             for (var i = 0, il = this.children.length; i < il; i++) {
  2310.                 if (this.children[i].hasOutputPlaceholder()) {
  2311.                     _arr.push(this.children[i]);
  2312.                 }
  2313.                 this.children[i].findElementsWithOutputPlaceholder(_arr);
  2314.             }
  2315.             return _arr;
  2316.         },
  2317.         
  2318.         /**
  2319.          * Paste content in context of current node. Pasting is a special case
  2320.          * of recursive adding content in node. 
  2321.          * This function will try to find $# placeholder inside node's 
  2322.          * attributes and text content and replace in with <code>text</code>.
  2323.          * If it doesn't find $# placeholder, it will put <code>text</code>
  2324.          * value as the deepest child content
  2325.          * @param {String} text Text to paste
  2326.          */
  2327.         pasteContent: function(text) {
  2328.             var symbol = '$#',
  2329.                 r = [symbol, text],
  2330.                 replace_fn = function() {return r;},
  2331.                 /** @type {ZenNode[]} */
  2332.                 items = [];
  2333.                 
  2334.             if (this.hasOutputPlaceholder())
  2335.                 items.push(this);
  2336.                 
  2337.             items = items.concat(this.findElementsWithOutputPlaceholder());
  2338.             
  2339.             if (items.length) {
  2340.                 for (var i = 0, il = items.length; i < il; i++) {
  2341.                     /** @type {ZenNode} */
  2342.                     var item = items[i];
  2343.                     item.content = replaceUnescapedSymbol(item.content, symbol, replace_fn);
  2344.                     for (var j = 0, jl = item.attributes.length; j < jl; j++) {
  2345.                         var a = item.attributes[j];
  2346.                         a.value = replaceUnescapedSymbol(a.value, symbol, replace_fn);
  2347.                     }
  2348.                 }
  2349.             } else {
  2350.                 // no placeholders found, add content to the deepest child
  2351.                 var child = this.findDeepestChild() || this;
  2352.                 child.content += text;
  2353.             }
  2354.         }
  2355.     };
  2356.     
  2357.     /**
  2358.      * Roll outs basic Zen Coding tree into simplified, DOM-like tree.
  2359.      * The simplified tree, for example, represents each multiplied element 
  2360.      * as a separate element sets with its own content, if exists.
  2361.      * 
  2362.      * The simplified tree element contains some meta info (tag name, attributes, 
  2363.      * etc.) as well as output strings, which are exactly what will be outputted
  2364.      * after expanding abbreviation. This tree is used for <i>filtering</i>:
  2365.      * you can apply filters that will alter output strings to get desired look
  2366.      * of expanded abbreviation.
  2367.      * 
  2368.      * @param {Tag} tree
  2369.      * @param {ZenNode} [parent]
  2370.      */
  2371.     function rolloutTree(tree, parent) {
  2372.         parent = parent || new ZenNode(tree);
  2373.         
  2374.         var how_many = 1,
  2375.             tag_content = '';
  2376.             
  2377.         if (tree.children) {
  2378.             for (var i = 0, il = tree.children.length; i < il; i++) {
  2379.                 /** @type {Tag} */
  2380.                 var child = tree.children[i];
  2381.                 how_many = child.count;
  2382.                 
  2383.                 if (child.repeat_by_lines) {
  2384.                     // it's a repeating element
  2385.                     tag_content = splitByLines(child.getPasteContent(), true);
  2386.                     how_many = Math.max(tag_content.length, 1);
  2387.                 } else {
  2388.                     tag_content = child.getPasteContent();
  2389.                 }
  2390.                 
  2391.                 for (var j = 0; j < how_many; j++) {
  2392.                     var tag = new ZenNode(child);
  2393.                     parent.addChild(tag);
  2394.                     tag.counter = j + 1;
  2395.                     
  2396.                     if (child.children && child.children.length)
  2397.                         rolloutTree(child, tag);
  2398.                         
  2399.                     if (tag_content) {
  2400.                         var text = (typeof(tag_content) == 'string') 
  2401.                             ? tag_content 
  2402.                             : (tag_content[j] || '');
  2403.                         tag.pasteContent(trim(text));
  2404.                     }
  2405.                 }
  2406.             }
  2407.         }
  2408.         
  2409.         return parent;
  2410.     }
  2411.     
  2412.     /**
  2413.      * Runs filters on tree
  2414.      * @param {ZenNode} tree
  2415.      * @param {String|Object} profile
  2416.      * @param {String[]|String} filter_list
  2417.      * @return {ZenNode}
  2418.      */
  2419.     function runFilters(tree, profile, filter_list) {
  2420.         profile = processProfile(profile);
  2421.         
  2422.         if (typeof(filter_list) == 'string')
  2423.             filter_list = filter_list.split(/[\|,]/g);
  2424.             
  2425.         for (var i = 0, il = filter_list.length; i < il; i++) {
  2426.             var name = trim(filter_list[i].toLowerCase());
  2427.             if (name && name in filters) {
  2428.                 tree = filters[name](tree, profile);
  2429.             }
  2430.         }
  2431.         
  2432.         return tree;
  2433.     }
  2434.     
  2435.     /**
  2436.      * Transforms abbreviation into a primary internal tree. This tree should'n 
  2437.      * be used ouside of this scope
  2438.      * @param {zen_parser.TreeNode} node Parsed tree node
  2439.      * @param {String} [type] Document type (xsl, html, etc.)
  2440.      * @return {Tag}
  2441.      */
  2442.     function transformTreeNode(node, type) {
  2443.         type = type || 'html';
  2444.         if (node.isEmpty()) return null;
  2445.         
  2446.         return isShippet(node.name, type) 
  2447.                 ? new Snippet(node, type)
  2448.                 : new Tag(node, type);
  2449.     }
  2450.     
  2451.     /**
  2452.      * Process single tree node: expand it and its children 
  2453.      * @param {zen_parser.TreeNode} node
  2454.      * @param {String} type
  2455.      * @param {Tag} parent
  2456.      */
  2457.     function processParsedNode(node, type, parent) {
  2458.         var t_node = transformTreeNode(node, type);
  2459.         parent.addChild(t_node);
  2460.             
  2461.         // set repeating element to the topmost node
  2462.         var root = parent;
  2463.         while (root.parent)
  2464.             root = root.parent;
  2465.         
  2466.         root.last = t_node;
  2467.         if (t_node.repeat_by_lines)
  2468.             root.multiply_elem = t_node;
  2469.             
  2470.         // process child groups
  2471.         for (var j = 0, jl = node.children.length; j < jl; j++) {
  2472.             processParsedNode(node.children[j], type, t_node);
  2473.         }
  2474.     }
  2475.     
  2476.     /**
  2477.      * Replaces expando nodes by its parsed content
  2478.      * @param {zen_parser.TreeNode} node
  2479.      * @param {String} type
  2480.      */
  2481.     function replaceExpandos(node, type) {
  2482.         for (var i = 0, il = node.children.length; i < il; i++) {
  2483.             var n = node.children[i];
  2484.             if (!n.isEmpty() && !n.isTextNode() && n.name.indexOf('+') != -1) {
  2485.                 // it's expando
  2486.                 var a = getAbbreviation(type, n.name);
  2487.                 if (a)
  2488.                     node.children[i] = zen_parser.parse(a.value);
  2489.             }
  2490.             replaceExpandos(node.children[i], type);
  2491.         }
  2492.     }
  2493.     
  2494.     /**
  2495.      * Replaces expandos and optimizes tree structure by removing empty nodes
  2496.      * @param {zen_parser.TreeNode} tree
  2497.      * @param {String} type
  2498.      */
  2499.     function preprocessParsedTree(tree, type) {
  2500.         replaceExpandos(tree, type);
  2501.         return zen_parser.optimizeTree(tree);
  2502.     }
  2503.     
  2504.     /**
  2505.      * Pad string with zeroes
  2506.      * @param {String} str
  2507.      * @param {Number} pad
  2508.      */
  2509.     function zeroPadString(str, pad) {
  2510.         var padding = '', 
  2511.             il = str.length;
  2512.             
  2513.         while (pad > il++) padding += '0';
  2514.         return padding + str; 
  2515.     }
  2516.     
  2517.     /**
  2518.      * Replaces unescaped symbols in <code>str</code>. For example, the '$' symbol
  2519.      * will be replaced in 'item$count', but not in 'item\$count'.
  2520.      * @param {String} str Original string
  2521.      * @param {String} symbol Symbol to replace
  2522.      * @param {String|Function} replace Symbol replacement
  2523.      * @return {String}
  2524.      */
  2525.     function replaceUnescapedSymbol(str, symbol, replace) {
  2526.         var i = 0,
  2527.             il = str.length,
  2528.             sl = symbol.length,
  2529.             match_count = 0;
  2530.             
  2531.         while (i < il) {
  2532.             if (str.charAt(i) == '\\') {
  2533.                 // escaped symbol, skip next character
  2534.                 i += sl + 1;
  2535.             } else if (str.substr(i, sl) == symbol) {
  2536.                 // have match
  2537.                 var cur_sl = sl;
  2538.                 match_count++;
  2539.                 var new_value = replace;
  2540.                 if (typeof(replace) !== 'string') {
  2541.                     var replace_data = replace(str, symbol, i, match_count);
  2542.                     if (replace_data) {
  2543.                         cur_sl = replace_data[0].length;
  2544.                         new_value = replace_data[1];
  2545.                     } else {
  2546.                         new_value = false;
  2547.                     }
  2548.                 }
  2549.                 
  2550.                 if (new_value === false) { // skip replacement
  2551.                     i++;
  2552.                     continue;
  2553.                 }
  2554.                 
  2555.                 str = str.substring(0, i) + new_value + str.substring(i + cur_sl);
  2556.                 // adjust indexes
  2557.                 il = str.length;
  2558.                 i += new_value.length;
  2559.             } else {
  2560.                 i++;
  2561.             }
  2562.         }
  2563.         
  2564.         return str;
  2565.     }
  2566.     
  2567.     /**
  2568.      * Processes profile argument, returning, if possible, profile object
  2569.      */
  2570.     function processProfile(profile) {
  2571.         var _profile = profile;
  2572.         if (typeof(profile) == 'string' && profile in profiles)
  2573.             _profile = profiles[profile];
  2574.         
  2575.         if (!_profile)
  2576.             _profile = profiles['plain'];
  2577.             
  2578.         return _profile;
  2579.     }
  2580.     
  2581.     // create default profiles
  2582.     setupProfile('xhtml');
  2583.     setupProfile('html', {self_closing_tag: false});
  2584.     setupProfile('xml', {self_closing_tag: true, tag_nl: true});
  2585.     setupProfile('plain', {tag_nl: false, indent: false, place_cursor: false});
  2586.     
  2587.     
  2588.     return {
  2589.         /** Hash of all available actions */
  2590.         actions: {},
  2591.         
  2592.         /**
  2593.          * Adds new Zen Coding action. This action will be available in
  2594.          * <code>zen_settings.actions</code> object.
  2595.          * @param {String} name Action's name
  2596.          * @param {Function} fn Action itself. The first argument should be
  2597.          * <code>zen_editor</code> instance.
  2598.          */
  2599.         registerAction: function(name, fn) {
  2600.             this.actions[name.toLowerCase()] = fn;
  2601.         },
  2602.         
  2603.         /**
  2604.          * Runs Zen Coding action. For list of available actions and their
  2605.          * arguments see <code>zen_actions.js</code> file.
  2606.          * @param {String} name Action name 
  2607.          * @param {Array} args Additional arguments. It may be array of arguments
  2608.          * or inline arguments. The first argument should be <code>zen_editor</code> instance
  2609.          * @example
  2610.          * zen_coding.runActions('expand_abbreviation', zen_editor);  
  2611.          * zen_coding.runActions('wrap_with_abbreviation', [zen_editor, 'div']);  
  2612.          */
  2613.         runAction: function(name, args) {
  2614.             if (!(args instanceof Array))
  2615.                 args = Array.prototype.slice.call(arguments, 1);
  2616.                 
  2617.             name = name.toLowerCase();
  2618.             if (name in this.actions)
  2619.                 return this.actions[name].apply(this, args);
  2620. //            try {
  2621. //            } catch(e){
  2622. //                if (window && window.console)
  2623. //                    console.error(e);
  2624. //                return false; 
  2625. //            }
  2626.         },
  2627.         
  2628.         expandAbbreviation: function(abbr, type, profile) {
  2629.             type = type || 'html';
  2630.             var parsed_tree = this.parseIntoTree(abbr, type);
  2631.             
  2632.             if (parsed_tree) {
  2633.                 var tree = rolloutTree(parsed_tree);
  2634.                 this.applyFilters(tree, type, profile, parsed_tree.filters);
  2635.                 return replaceVariables(tree.toString());
  2636.             }
  2637.             
  2638.             return '';
  2639.         },
  2640.         
  2641.         /**
  2642.          * Extracts abbreviations from text stream, starting from the end
  2643.          * @param {String} str
  2644.          * @return {String} Abbreviation or empty string
  2645.          */
  2646.         extractAbbreviation: function(str) {
  2647.             var cur_offset = str.length,
  2648.                 start_index = -1,
  2649.                 group_count = 0,
  2650.                 brace_count = 0,
  2651.                 text_count = 0;
  2652.             
  2653.             while (true) {
  2654.                 cur_offset--;
  2655.                 if (cur_offset < 0) {
  2656.                     // moved to the beginning of the line
  2657.                     start_index = 0;
  2658.                     break;
  2659.                 }
  2660.                 
  2661.                 var ch = str.charAt(cur_offset);
  2662.                 
  2663.                 if (ch == ']') {
  2664.                     brace_count++;
  2665.                 } else if (ch == '[') {
  2666.                     if (!brace_count) { // unexpected brace
  2667.                         start_index = cur_offset + 1;
  2668.                         break;
  2669.                     }
  2670.                     brace_count--;
  2671.                 } else if (ch == '}') {
  2672.                     text_count++;
  2673.                 } else if (ch == '{') {
  2674.                     if (!text_count) { // unexpected brace
  2675.                         start_index = cur_offset + 1;
  2676.                         break;
  2677.                     }
  2678.                     text_count--;
  2679.                 } else if (ch == ')') {
  2680.                     group_count++;
  2681.                 } else if (ch == '(') {
  2682.                     if (!group_count) { // unexpected brace
  2683.                         start_index = cur_offset + 1;
  2684.                         break;
  2685.                     }
  2686.                     group_count--;
  2687.                 } else {
  2688.                     if (brace_count || text_count) 
  2689.                         // respect all characters inside attribute sets or text nodes
  2690.                         continue;
  2691.                     else if (!isAllowedChar(ch) || (ch == '>' && isEndsWithTag(str.substring(0, cur_offset + 1)))) {
  2692.                         // found stop symbol
  2693.                         start_index = cur_offset + 1;
  2694.                         break;
  2695.                     }
  2696.                 }
  2697.             }
  2698.             
  2699.             if (start_index != -1 && !text_count && !brace_count && !group_count) 
  2700.                 // found something, return abbreviation
  2701.                 return str.substring(start_index);
  2702.             else
  2703.                 return '';
  2704.         },
  2705.         
  2706.         /**
  2707.          * Parses abbreviation into a node set
  2708.          * @param {String} abbr Abbreviation
  2709.          * @param {String} type Document type (xsl, html, etc.)
  2710.          * @return {Tag}
  2711.          */
  2712.         parseIntoTree: function(abbr, type) {
  2713.             type = type || 'html';
  2714.             // remove filters from abbreviation
  2715.             var filter_list = '';
  2716.             abbr = abbr.replace(/\|([\w\|\-]+)$/, function(str, p1){
  2717.                 filter_list = p1;
  2718.                 return '';
  2719.             });
  2720.             
  2721.             // try to parse abbreviation
  2722.             try {
  2723.                 var abbr_tree = zen_parser.parse(abbr),
  2724.                     tree_root = new Tag({}, type);
  2725.                     
  2726.                 abbr_tree = preprocessParsedTree(abbr_tree, type);
  2727.             } catch(e) {
  2728.                 if (e.message == "InvalidAbbreviation")
  2729.                     return null;
  2730.             }
  2731.                 
  2732.             // then recursively expand each group item
  2733.             for (var i = 0, il = abbr_tree.children.length; i < il; i++) {
  2734.                 processParsedNode(abbr_tree.children[i], type, tree_root);
  2735.             }
  2736.             
  2737.             tree_root.filters = filter_list;
  2738.             return tree_root;
  2739.         },
  2740.         
  2741.         /**
  2742.          * Indents text with padding
  2743.          * @param {String} text Text to indent
  2744.          * @param {String|Number} pad Padding size (number) or padding itself (string)
  2745.          * @return {String}
  2746.          */
  2747.         padString: padString,
  2748.         setupProfile: setupProfile,
  2749.         getNewline: function(){
  2750.             return newline;
  2751.         },
  2752.         
  2753.         setNewline: function(str) {
  2754.             newline = str;
  2755.             this.setVariable('newline', str);
  2756.             this.setVariable('nl', str);
  2757.         },
  2758.         
  2759.         /**
  2760.          * Wraps passed text with abbreviation. Text will be placed inside last
  2761.          * expanded element
  2762.          * @param {String} abbr Abbreviation
  2763.          * @param {String} text Text to wrap
  2764.          * @param {String} [type] Document type (html, xml, etc.). Default is 'html'
  2765.          * @param {String} [profile] Output profile's name. Default is 'plain'
  2766.          * @return {String}
  2767.          */
  2768.         wrapWithAbbreviation: function(abbr, text, type, profile) {
  2769.             type = type || 'html';
  2770.             var tree_root = this.parseIntoTree(abbr, type),
  2771.                 pasted = false;
  2772.                 
  2773.             if (tree_root) {
  2774.                 if (tree_root.multiply_elem) {
  2775.                     // we have a repeating element, put content in
  2776.                     tree_root.multiply_elem.setPasteContent(text);
  2777.                     tree_root.multiply_elem.repeat_by_lines = pasted = true;
  2778.                 }
  2779.                 
  2780.                 var tree = rolloutTree(tree_root);
  2781.                 
  2782.                 if (!pasted) 
  2783.                     tree.pasteContent(text);
  2784.                 
  2785.                 this.applyFilters(tree, type, profile, tree_root.filters);
  2786.                 return replaceVariables(tree.toString());
  2787.             }
  2788.             
  2789.             return null;
  2790.         },
  2791.         
  2792.         splitByLines: splitByLines,
  2793.         
  2794.         /**
  2795.          * Check if cursor is placed inside xHTML tag
  2796.          * @param {String} html Contents of the document
  2797.          * @param {Number} cursor_pos Current caret position inside tag
  2798.          * @return {Boolean}
  2799.          */
  2800.         isInsideTag: function(html, cursor_pos) {
  2801.             var re_tag = /^<\/?\w[\w\:\-]*.*?>/;
  2802.             
  2803.             // search left to find opening brace
  2804.             var pos = cursor_pos;
  2805.             while (pos > -1) {
  2806.                 if (html.charAt(pos) == '<') 
  2807.                     break;
  2808.                 pos--;
  2809.             }
  2810.             
  2811.             if (pos != -1) {
  2812.                 var m = re_tag.exec(html.substring(pos));
  2813.                 if (m && cursor_pos > pos && cursor_pos < pos + m[0].length)
  2814.                     return true;
  2815.             }
  2816.             
  2817.             return false;
  2818.         },
  2819.         
  2820.         /**
  2821.          * Returns caret placeholder
  2822.          * @return {String}
  2823.          */
  2824.         getCaretPlaceholder: getCaretPlaceholder,
  2825.         
  2826.         /**
  2827.          * Set caret placeholder: a string (like '|') or function.
  2828.          * You may use a function as a placeholder generator. For example,
  2829.          * TextMate uses ${0}, ${1}, ..., ${n} natively for quick Tab-switching
  2830.          * between them.
  2831.          * @param {String|Function} value
  2832.          */
  2833.         setCaretPlaceholder: function(value) {
  2834.             caret_placeholder = value;
  2835.         },
  2836.         
  2837.         rolloutTree: rolloutTree,
  2838.         
  2839.         /**
  2840.          * Register new filter
  2841.          * @param {String} name Filter name
  2842.          * @param {Function} fn Filter function
  2843.          */
  2844.         registerFilter: function(name, fn) {
  2845.             filters[name] = fn;
  2846.         },
  2847.         
  2848.         /**
  2849.          * Factory method that produces <code>ZenNode</code> instance
  2850.          * @param {String} name Node name
  2851.          * @param {Array} [attrs] Array of attributes as key/value objects  
  2852.          * @return {ZenNode}
  2853.          */
  2854.         nodeFactory: function(name, attrs) {
  2855.             return new ZenNode({name: name, attributes: attrs || []});
  2856.         },
  2857.         
  2858.         /**
  2859.          * Applies filters to tree according to syntax
  2860.          * @param {ZenNode} tree Tag tree to apply filters to
  2861.          * @param {String} syntax Syntax name ('html', 'css', etc.)
  2862.          * @param {String|Object} profile Profile or profile's name
  2863.          * @param {String|Array} [additional_filters] List or pipe-separated 
  2864.          * string of additional filters to apply
  2865.          * 
  2866.          * @return {ZenNode}
  2867.          */
  2868.         applyFilters: function(tree, syntax, profile, additional_filters) {
  2869.             profile = processProfile(profile);
  2870.             var _filters = profile.filters;
  2871.             if (!_filters)
  2872.                 _filters = zen_resources.getSubset(syntax, 'filters') || basic_filters;
  2873.                 
  2874.             if (additional_filters)
  2875.                 _filters += '|' + ((typeof(additional_filters) == 'string') 
  2876.                     ? additional_filters 
  2877.                     : additional_filters.join('|'));
  2878.                 
  2879.             if (!_filters)
  2880.                 // looks like unknown syntax, apply basic filters
  2881.                 _filters = basic_filters;
  2882.                 
  2883.             return runFilters(tree, profile, _filters);
  2884.         },
  2885.         
  2886.         runFilters: runFilters,
  2887.         
  2888.         repeatString: repeatString,
  2889.         getVariable: getVariable,
  2890.         /**
  2891.          * Store runtime variable in user storage
  2892.          * @param {String} name Variable name
  2893.          * @param {String} value Variable value
  2894.          */
  2895.         setVariable: function(name, value){
  2896.             var voc = zen_resources.getVocabulary('user') || {};
  2897.             if (!('varaibles' in voc))
  2898.                 voc.variables = {};
  2899.                 
  2900.             voc.variables[name] = value;
  2901.             zen_resources.setVocabulary(voc, 'user');
  2902.         },
  2903.         replaceVariables: replaceVariables,
  2904.         
  2905.         /**
  2906.          * Escapes special characters used in Zen Coding, like '$', '|', etc.
  2907.          * Use this method before passing to actions like "Wrap with Abbreviation"
  2908.          * to make sure that existing spacial characters won't be altered
  2909.          * @param {String} text
  2910.          * @return {String}
  2911.          */
  2912.         escapeText: function(text) {
  2913.             return text.replace(/([\$\|\\])/g, '\\$1');
  2914.         },
  2915.         
  2916.         /**
  2917.          * Unescapes special characters used in Zen Coding, like '$', '|', etc.
  2918.          * @param {String} text
  2919.          * @return {String}
  2920.          */
  2921.         unescapeText: function(text) {
  2922.             return text.replace(/\\(.)/g, '$1');
  2923.         },
  2924.         
  2925.         /**
  2926.          * Replaces '$' character in string assuming it might be escaped with '\'
  2927.          * @param {String} str
  2928.          * @param {String|Number} value
  2929.          * @return {String}
  2930.          */
  2931.         replaceCounter: function(str, value) {
  2932.             var symbol = '$';
  2933.             value = String(value);
  2934.             return replaceUnescapedSymbol(str, symbol, function(str, symbol, pos, match_num){
  2935.                 if (str.charAt(pos + 1) == '{' || isNumeric(str.charAt(pos + 1)) ) {
  2936.                     // it's a variable, skip it
  2937.                     return false;
  2938.                 }
  2939.                 
  2940.                 // replace sequense of $ symbols with padded number  
  2941.                 var j = pos + 1;
  2942.                 while(str.charAt(j) == '$' && str.charAt(j + 1) != '{') j++;
  2943.                 return [str.substring(pos, j), zeroPadString(value, j - pos)];
  2944.             });
  2945.         },
  2946.         
  2947.         isNumeric: isNumeric,
  2948.         
  2949.         /**
  2950.          * Upgrades tabstops in zen node in order to prevent naming conflicts
  2951.          * @param {ZenNode} node
  2952.          * @param {Number} offset Tab index offset
  2953.          * @returns {Number} Maximum tabstop index in element
  2954.          */
  2955.         upgradeTabstops: function(node, offset) {
  2956.             var max_num = 0,
  2957.                 props = ['start', 'end', 'content'],
  2958.                 escape_fn = function(ch){ return '\\' + ch; },
  2959.                 tabstop_fn = function(i, num, value) {
  2960.                     num = parseInt(num);
  2961.                     if (num > max_num) max_num = num;
  2962.                         
  2963.                     if (value)
  2964.                         return '${' + (num + offset) + ':' + value + '}';
  2965.                     else
  2966.                         return '$' + (num + offset);
  2967.                 };
  2968.                 
  2969.             for (var i = 0, il = props.length; i < il; i++)
  2970.                 node[props[i]] = this.processTextBeforePaste(node[props[i]], escape_fn, tabstop_fn);
  2971.             
  2972.             return max_num;
  2973.         },
  2974.         
  2975.         /**
  2976.          * Get profile by it's name. If profile wasn't found, returns 'plain'
  2977.          * profile
  2978.          */
  2979.         getProfile: function(name) {
  2980.             return (name in profiles) ? profiles[name] : profiles['plain'];
  2981.         },
  2982.         
  2983.         /**
  2984.          * Gets image size from image byte stream.
  2985.          * @author http://romeda.org/rePublish/
  2986.          * @param {String} stream Image byte stream (use <code>zen_file.read()</code>)
  2987.          * @return {Object} Object with <code>width</code> and <code>height</code> properties
  2988.          */
  2989.         getImageSize: function(stream) {
  2990.             var pngMagicNum = "\211PNG\r\n\032\n",
  2991.                 jpgMagicNum = "\377\330",
  2992.                 gifMagicNum = "GIF8",
  2993.                 nextByte = function() {
  2994.                     return stream.charCodeAt(pos++);
  2995.                 };
  2996.         
  2997.             if (stream.substr(0, 8) === pngMagicNum) {
  2998.                 // PNG. Easy peasy.
  2999.                 var pos = stream.indexOf('IHDR') + 4;
  3000.             
  3001.                 return { width:  (nextByte() << 24) | (nextByte() << 16) |
  3002.                                  (nextByte() <<  8) | nextByte(),
  3003.                          height: (nextByte() << 24) | (nextByte() << 16) |
  3004.                                  (nextByte() <<  8) | nextByte() };
  3005.             
  3006.             } else if (stream.substr(0, 4) === gifMagicNum) {
  3007.                 pos = 6;
  3008.             
  3009.                 return {
  3010.                     width:  nextByte() | (nextByte() << 8),
  3011.                     height: nextByte() | (nextByte() << 8)
  3012.                 };
  3013.             
  3014.             } else if (stream.substr(0, 2) === jpgMagicNum) {
  3015.                 // TODO need testing
  3016.                 pos = 2;
  3017.             
  3018.                 var l = stream.length;
  3019.                 while (pos < l) {
  3020.                     if (nextByte() != 0xFF) return;
  3021.                 
  3022.                     var marker = nextByte();
  3023.                     if (marker == 0xDA) break;
  3024.                 
  3025.                     var size = (nextByte() << 8) | nextByte();
  3026.                 
  3027.                     if (marker >= 0xC0 && marker <= 0xCF && !(marker & 0x4) && !(marker & 0x8)) {
  3028.                         pos += 1;
  3029.                         return { height:  (nextByte() << 8) | nextByte(),
  3030.                                  width: (nextByte() << 8) | nextByte() };
  3031.                 
  3032.                     } else {
  3033.                         pos += size - 2;
  3034.                     }
  3035.                 }
  3036.             }
  3037.         },
  3038.         
  3039.         /**
  3040.          * Returns context-aware node counter
  3041.          * @param {node} ZenNode
  3042.          * @return {Number}
  3043.          */
  3044.         getCounterForNode: function(node) {
  3045.             // find nearest repeating parent
  3046.             var counter = node.counter;
  3047.             if (!node.is_repeating && !node.repeat_by_lines) {
  3048.                 while (node = node.parent) {
  3049.                     if (node.is_repeating || node.repeat_by_lines)
  3050.                         return node.counter;
  3051.                 }
  3052.             }
  3053.             
  3054.             return counter;
  3055.         },
  3056.         
  3057.         /**
  3058.          * Process text that should be pasted into editor: clear escaped text and
  3059.          * handle tabstops
  3060.          * @param {String} text
  3061.          * @param {Function} escape_fn Handle escaped character. Must return
  3062.          * replaced value
  3063.          * @param {Function} tabstop_fn Callback function that will be called on every
  3064.          * tabstob occurance, passing <b>index</b>, <code>number</code> and 
  3065.          * <b>value</b> (if exists) arguments. This function must return 
  3066.          * replacement value
  3067.          * @return {String} 
  3068.          */
  3069.         processTextBeforePaste: function(text, escape_fn, tabstop_fn) {
  3070.             var i = 0, il = text.length, start_ix, _i,
  3071.                 str_builder = [];
  3072.                 
  3073.             var nextWhile = function(ix, fn) {
  3074.                 while (ix < il) if (!fn(text.charAt(ix++))) break;
  3075.                 return ix - 1;
  3076.             };
  3077.             
  3078.             while (i < il) {
  3079.                 var ch = text.charAt(i);
  3080.                 if (ch == '\\' && i + 1 < il) {
  3081.                     // handle escaped character
  3082.                     str_builder.push(escape_fn(text.charAt(i + 1)));
  3083.                     i += 2;
  3084.                     continue;
  3085.                 } else if (ch == '$') {
  3086.                     // looks like a tabstop
  3087.                     var next_ch = text.charAt(i + 1) || '';
  3088.                     _i = i;
  3089.                     if (this.isNumeric(next_ch)) {
  3090.                         // $N placeholder
  3091.                         start_ix = i + 1;
  3092.                         i = nextWhile(start_ix, this.isNumeric);
  3093.                         if (start_ix < i) {
  3094.                             str_builder.push(tabstop_fn(_i, text.substring(start_ix, i)));
  3095.                             continue;
  3096.                         }
  3097.                     } else if (next_ch == '{') {
  3098.                         // ${N:value} or ${N} placeholder
  3099.                         var brace_count = 1;
  3100.                         start_ix = i + 2;
  3101.                         i = nextWhile(start_ix, this.isNumeric);
  3102.                         
  3103.                         if (i > start_ix) {
  3104.                             if (text.charAt(i) == '}') {
  3105.                                 str_builder.push(tabstop_fn(_i, text.substring(start_ix, i)));
  3106.                                 i++; // handle closing brace
  3107.                                 continue;
  3108.                             } else if (text.charAt(i) == ':') {
  3109.                                 var val_start = i + 2;
  3110.                                 i = nextWhile(val_start, function(c) {
  3111.                                     if (c == '{') brace_count++;
  3112.                                     else if (c == '}') brace_count--;
  3113.                                     return !!brace_count;
  3114.                                 });
  3115.                                 str_builder.push(tabstop_fn(_i, text.substring(start_ix, val_start - 2), text.substring(val_start - 1, i)));
  3116.                                 i++; // handle closing brace
  3117.                                 continue;
  3118.                             }
  3119.                         }
  3120.                     }
  3121.                     i = _i;
  3122.                 }
  3123.                 
  3124.                 // push current character to stack
  3125.                 str_builder.push(ch);
  3126.                 i++;
  3127.             }
  3128.             
  3129.             return str_builder.join('');
  3130.         }
  3131.     }
  3132. })();/**
  3133.  * Middleware layer that communicates between editor and Zen Coding.
  3134.  * This layer describes all available Zen Coding actions, like 
  3135.  * "Expand Abbreviation".
  3136.  * @author Sergey Chikuyonok (serge.che@gmail.com)
  3137.  * @link http://chikuyonok.ru
  3138.  * 
  3139.  * @include "zen_editor.js"
  3140.  * @include "html_matcher.js"
  3141.  * @include "zen_coding.js"
  3142.  * @include "zen_file.js"
  3143.  * @include "base64.js"
  3144.  */
  3145.  
  3146. /**
  3147.  * Search for abbreviation in editor from current caret position
  3148.  * @param {zen_editor} editor Editor instance
  3149.  * @return {String|null}
  3150.  */
  3151. function findAbbreviation(editor) {
  3152.     var range = editor.getSelectionRange(),
  3153.         content = String(editor.getContent());
  3154.     if (range.start != range.end) {
  3155.         // abbreviation is selected by user
  3156.         return content.substring(range.start, range.end);
  3157.     }
  3158.     
  3159.     // search for new abbreviation from current caret position
  3160.     var cur_line = editor.getCurrentLineRange();
  3161.     return zen_coding.extractAbbreviation(content.substring(cur_line.start, range.start));
  3162. }
  3163.  
  3164. /**
  3165.  * Find from current caret position and expand abbreviation in editor
  3166.  * @param {zen_editor} editor Editor instance
  3167.  * @param {String} [syntax] Syntax type (html, css, etc.)
  3168.  * @param {String} [profile_name] Output profile name (html, xml, xhtml)
  3169.  * @return {Boolean} Returns <code>true</code> if abbreviation was expanded 
  3170.  * successfully
  3171.  */
  3172. function expandAbbreviation(editor, syntax, profile_name) {
  3173.     syntax = String(syntax || editor.getSyntax());
  3174.     profile_name = String(profile_name || editor.getProfileName());
  3175.     
  3176.     var caret_pos = editor.getSelectionRange().end,
  3177.         abbr,
  3178.         content = '';
  3179.         
  3180.     if ( (abbr = findAbbreviation(editor)) ) {
  3181.         content = zen_coding.expandAbbreviation(abbr, syntax, profile_name);
  3182.         if (content) {
  3183.             editor.replaceContent(content, caret_pos - abbr.length, caret_pos);
  3184.             return true;
  3185.         }
  3186.     }
  3187.     
  3188.     return false;
  3189. }
  3190.  
  3191. /**
  3192.  * A special version of <code>expandAbbreviation</code> function: if it can't
  3193.  * find abbreviation, it will place Tab character at caret position
  3194.  * @param {zen_editor} editor Editor instance
  3195.  * @param {String} syntax Syntax type (html, css, etc.)
  3196.  * @param {String} profile_name Output profile name (html, xml, xhtml)
  3197.  */
  3198. function expandAbbreviationWithTab(editor, syntax, profile_name) {
  3199.     syntax = String(syntax || editor.getSyntax());
  3200.     profile_name = String(profile_name || editor.getProfileName());
  3201.     if (!expandAbbreviation(editor, syntax, profile_name))
  3202.         editor.replaceContent(zen_coding.getVariable('indentation'), editor.getCaretPos());
  3203. }
  3204.  
  3205. /**
  3206.  * Find and select HTML tag pair
  3207.  * @param {zen_editor} editor Editor instance
  3208.  * @param {String} [direction] Direction of pair matching: 'in' or 'out'. 
  3209.  * Default is 'out'
  3210.  */
  3211. function matchPair(editor, direction, syntax) {
  3212.     direction = String((direction || 'out').toLowerCase());
  3213.     syntax = String(syntax || editor.getProfileName());
  3214.     
  3215.     var range = editor.getSelectionRange(),
  3216.         cursor = range.end,
  3217.         range_start = range.start, 
  3218.         range_end = range.end,
  3219. //        content = zen_coding.splitByLines(editor.getContent()).join('\n'),
  3220.         content = String(editor.getContent()),
  3221.         range = null,
  3222.         _r,
  3223.     
  3224.         old_open_tag = zen_coding.html_matcher.last_match['opening_tag'],
  3225.         old_close_tag = zen_coding.html_matcher.last_match['closing_tag'];
  3226.         
  3227.     if (direction == 'in' && old_open_tag && range_start != range_end) {
  3228. //        user has previously selected tag and wants to move inward
  3229.         if (!old_close_tag) {
  3230. //            unary tag was selected, can't move inward
  3231.             return false;
  3232.         } else if (old_open_tag.start == range_start) {
  3233.             if (content.charAt(old_open_tag.end) == '<') {
  3234. //                test if the first inward tag matches the entire parent tag's content
  3235.                 _r = zen_coding.html_matcher.find(content, old_open_tag.end + 1, syntax);
  3236.                 if (_r[0] == old_open_tag.end && _r[1] == old_close_tag.start) {
  3237.                     range = zen_coding.html_matcher(content, old_open_tag.end + 1, syntax);
  3238.                 } else {
  3239.                     range = [old_open_tag.end, old_close_tag.start];
  3240.                 }
  3241.             } else {
  3242.                 range = [old_open_tag.end, old_close_tag.start];
  3243.             }
  3244.         } else {
  3245.             var new_cursor = content.substring(0, old_close_tag.start).indexOf('<', old_open_tag.end);
  3246.             var search_pos = new_cursor != -1 ? new_cursor + 1 : old_open_tag.end;
  3247.             range = zen_coding.html_matcher(content, search_pos, syntax);
  3248.         }
  3249.     } else {
  3250.         range = zen_coding.html_matcher(content, cursor, syntax);
  3251.     }
  3252.     
  3253.     if (range !== null && range[0] != -1) {
  3254.         editor.createSelection(range[0], range[1]);
  3255.         return true;
  3256.     } else {
  3257.         return false;
  3258.     }
  3259. }
  3260.  
  3261. /**
  3262.  * Narrow down text indexes, adjusting selection to non-space characters
  3263.  * @param {String} text
  3264.  * @param {Number} start
  3265.  * @param {Number} end
  3266.  * @return {Array}
  3267.  */
  3268. function narrowToNonSpace(text, start, end) {
  3269.     // narrow down selection until first non-space character
  3270.     var re_space = /\s|\n|\r/;
  3271.     function isSpace(ch) {
  3272.         return re_space.test(ch);
  3273.     }
  3274.     
  3275.     while (start < end) {
  3276.         if (!isSpace(text.charAt(start)))
  3277.             break;
  3278.             
  3279.         start++;
  3280.     }
  3281.     
  3282.     while (end > start) {
  3283.         end--;
  3284.         if (!isSpace(text.charAt(end))) {
  3285.             end++;
  3286.             break;
  3287.         }
  3288.     }
  3289.     
  3290.     return [start, end];
  3291. }
  3292.  
  3293. /**
  3294.  * Wraps content with abbreviation
  3295.  * @param {zen_editor} Editor instance
  3296.  * @param {String} abbr Abbreviation to wrap with
  3297.  * @param {String} [syntax] Syntax type (html, css, etc.)
  3298.  * @param {String} [profile_name] Output profile name (html, xml, xhtml)
  3299.  */
  3300. function wrapWithAbbreviation(editor, abbr, syntax, profile_name) {
  3301.     syntax = String(syntax || editor.getSyntax());
  3302.     profile_name = String(profile_name || editor.getProfileName());
  3303.     abbr = abbr || editor.prompt("Enter abbreviation");
  3304.     
  3305.     var range = editor.getSelectionRange(),
  3306.         start_offset = range.start,
  3307.         end_offset = range.end,
  3308.         content = String(editor.getContent());
  3309.         
  3310.     if (!abbr || typeof abbr == 'undefined')
  3311.         return null; 
  3312.         
  3313.     abbr = String(abbr);
  3314.     
  3315.     if (start_offset == end_offset) {
  3316.         // no selection, find tag pair
  3317.         range = zen_coding.html_matcher(content, start_offset, profile_name);
  3318.         
  3319.         if (!range || range[0] == -1) // nothing to wrap
  3320.             return null;
  3321.         
  3322.         var narrowed_sel = narrowToNonSpace(content, range[0], range[1]);
  3323.         
  3324.         start_offset = narrowed_sel[0];
  3325.         end_offset = narrowed_sel[1];
  3326.     }
  3327.     
  3328.     var new_content = zen_coding.escapeText(content.substring(start_offset, end_offset)),
  3329.         result = zen_coding.wrapWithAbbreviation(abbr, unindent(editor, new_content), syntax, profile_name);
  3330.     
  3331.     if (result) {
  3332.         editor.setCaretPos(end_offset);
  3333.         editor.replaceContent(result, start_offset, end_offset);
  3334.     }
  3335. }
  3336.  
  3337. /**
  3338.  * Unindent content, thus preparing text for tag wrapping
  3339.  * @param {zen_editor} editor Editor instance
  3340.  * @param {String} text
  3341.  * @return {String}
  3342.  */
  3343. function unindent(editor, text) {
  3344.     return unindentText(text, getCurrentLinePadding(editor));
  3345. }
  3346.  
  3347. /**
  3348.  * Removes padding at the beginning of each text's line
  3349.  * @param {String} text
  3350.  * @param {String} pad
  3351.  */
  3352. function unindentText(text, pad) {
  3353.     var lines = zen_coding.splitByLines(text);
  3354.     for (var i = 0; i < lines.length; i++) {
  3355.         if (lines[i].search(pad) == 0)
  3356.             lines[i] = lines[i].substr(pad.length);
  3357.     }
  3358.     
  3359.     return lines.join(zen_coding.getNewline());
  3360. }
  3361.  
  3362. /**
  3363.  * Returns padding of current editor's line
  3364.  * @param {zen_editor} Editor instance
  3365.  * @return {String}
  3366.  */
  3367. function getCurrentLinePadding(editor) {
  3368.     return getLinePadding(editor.getCurrentLine());
  3369. }
  3370.  
  3371. /**
  3372.  * Returns line padding
  3373.  * @param {String} line
  3374.  * @return {String}
  3375.  */
  3376. function getLinePadding(line) {
  3377.     return (line.match(/^(\s+)/) || [''])[0];
  3378. }
  3379.  
  3380. /**
  3381.  * Search for new caret insertion point
  3382.  * @param {zen_editor} editor Editor instance
  3383.  * @param {Number} inc Search increment: -1 ΓÇö search left, 1 ΓÇö search right
  3384.  * @param {Number} offset Initial offset relative to current caret position
  3385.  * @return {Number} Returns -1 if insertion point wasn't found
  3386.  */
  3387. function findNewEditPoint(editor, inc, offset) {
  3388.     inc = inc || 1;
  3389.     offset = offset || 0;
  3390.     var cur_point = editor.getCaretPos() + offset,
  3391.         content = String(editor.getContent()),
  3392.         max_len = content.length,
  3393.         next_point = -1,
  3394.         re_empty_line = /^\s+$/;
  3395.     
  3396.     function ch(ix) {
  3397.         return content.charAt(ix);
  3398.     }
  3399.     
  3400.     function getLine(ix) {
  3401.         var start = ix;
  3402.         while (start >= 0) {
  3403.             var c = ch(start);
  3404.             if (c == '\n' || c == '\r')
  3405.                 break;
  3406.             start--;
  3407.         }
  3408.         
  3409.         return content.substring(start, ix);
  3410.     }
  3411.         
  3412.     while (cur_point < max_len && cur_point > 0) {
  3413.         cur_point += inc;
  3414.         var cur_char = ch(cur_point),
  3415.             next_char = ch(cur_point + 1),
  3416.             prev_char = ch(cur_point - 1);
  3417.             
  3418.         switch (cur_char) {
  3419.             case '"':
  3420.             case '\'':
  3421.                 if (next_char == cur_char && prev_char == '=') {
  3422.                     // empty attribute
  3423.                     next_point = cur_point + 1;
  3424.                 }
  3425.                 break;
  3426.             case '>':
  3427.                 if (next_char == '<') {
  3428.                     // between tags
  3429.                     next_point = cur_point + 1;
  3430.                 }
  3431.                 break;
  3432.             case '\n':
  3433.             case '\r':
  3434.                 // empty line
  3435.                 if (re_empty_line.test(getLine(cur_point - 1))) {
  3436.                     next_point = cur_point;
  3437.                 }
  3438.                 break;
  3439.         }
  3440.         
  3441.         if (next_point != -1)
  3442.             break;
  3443.     }
  3444.     
  3445.     return next_point;
  3446. }
  3447.  
  3448. /**
  3449.  * Move caret to previous edit point
  3450.  * @param {zen_editor} editor Editor instance
  3451.  */
  3452. function prevEditPoint(editor) {
  3453.     var cur_pos = editor.getCaretPos(),
  3454.         new_point = findNewEditPoint(editor, -1);
  3455.         
  3456.     if (new_point == cur_pos)
  3457.         // we're still in the same point, try searching from the other place
  3458.         new_point = findNewEditPoint(editor, -1, -2);
  3459.     
  3460.     if (new_point != -1) 
  3461.         editor.setCaretPos(new_point);
  3462. }
  3463.  
  3464. /**
  3465.  * Move caret to next edit point
  3466.  * @param {zen_editor} editor Editor instance
  3467.  */
  3468. function nextEditPoint(editor) {
  3469.     var new_point = findNewEditPoint(editor, 1);
  3470.     if (new_point != -1)
  3471.         editor.setCaretPos(new_point);
  3472. }
  3473.  
  3474. /**
  3475.  * Inserts newline character with proper indentation in specific positions only.
  3476.  * @param {zen_editor} editor
  3477.  * @return {Boolean} Returns <code>true</code> if line break was inserted 
  3478.  */
  3479. function insertFormattedNewlineOnly(editor) {
  3480.     var caret_pos = editor.getCaretPos(),
  3481.         content = String(editor.getContent()),
  3482.         nl = zen_coding.getNewline(),
  3483.         pad = zen_coding.getVariable('indentation'),
  3484.         syntax = String(editor.getSyntax());
  3485.         
  3486.     if (syntax == 'html') {
  3487.         // let's see if we're breaking newly created tag
  3488.         var pair = zen_coding.html_matcher.getTags(content, caret_pos, String(editor.getProfileName()));
  3489.         
  3490.         if (pair[0] && pair[1] && pair[0].type == 'tag' && pair[0].end == caret_pos && pair[1].start == caret_pos) {
  3491.             editor.replaceContent(nl + pad + zen_coding.getCaretPlaceholder() + nl, caret_pos);
  3492.             return true;
  3493.         }
  3494.     } else if (syntax == 'css') {
  3495.         if (caret_pos && content.charAt(caret_pos - 1) == '{') {
  3496.             // look ahead for a closing brace
  3497.             for (var i = caret_pos, il = content.length, ch; i < il; i++) {
  3498.                 ch = content.charAt(i);
  3499.                 if (ch == '}') return false;
  3500.                 if (ch == '{') break;
  3501.             }
  3502.             
  3503.             // defining rule set
  3504.             var ins_value = nl + pad + zen_coding.getCaretPlaceholder() + nl,
  3505.                 has_close_brace = caret_pos < content.length && content.charAt(caret_pos) == '}';
  3506.                 
  3507.             var user_close_brace = zen_coding.getVariable('close_css_brace');
  3508.             if (user_close_brace) {
  3509.                 // user defined how close brace should look like
  3510.                 ins_value += zen_coding.replaceVariables(user_close_brace);
  3511.             } else if (!has_close_brace) {
  3512.                 ins_value += '}';
  3513.             }
  3514.             
  3515.             editor.replaceContent(ins_value, caret_pos, caret_pos + (has_close_brace ? 1 : 0));
  3516.             return true;
  3517.         }
  3518.     }
  3519.         
  3520.     return false;
  3521. }
  3522.  
  3523. /**
  3524.  * Inserts newline character with proper indentation. This action is used in
  3525.  * editors that doesn't have indentation control (like textarea element) to 
  3526.  * provide proper indentation
  3527.  * @param {zen_editor} editor Editor instance
  3528.  */
  3529. function insertFormattedNewline(editor) {
  3530.     if (!insertFormattedNewlineOnly(editor)) {
  3531.         var cur_padding = getCurrentLinePadding(editor),
  3532.             content = String(editor.getContent()),
  3533.             caret_pos = editor.getCaretPos(),
  3534.             c_len = content.length,
  3535.             nl = zen_coding.getNewline();
  3536.             
  3537.         // check out next line padding
  3538.         var line_range = editor.getCurrentLineRange(),
  3539.             next_padding = '';
  3540.             
  3541.         for (var i = line_range.end + 1, ch; i < c_len; i++) {
  3542.             ch = content.charAt(i);
  3543.             if (ch == ' ' || ch == '\t')
  3544.                 next_padding += ch;
  3545.             else
  3546.                 break;
  3547.         }
  3548.         
  3549.         if (next_padding.length > cur_padding.length)
  3550.             editor.replaceContent(nl + next_padding, caret_pos, caret_pos, true);
  3551.         else
  3552.             editor.replaceContent(nl, caret_pos);
  3553.     }
  3554. }
  3555.  
  3556. /**
  3557.  * Select line under cursor
  3558.  * @param {zen_editor} editor Editor instance
  3559.  */
  3560. function selectLine(editor) {
  3561.     var range = editor.getCurrentLineRange();
  3562.     editor.createSelection(range.start, range.end);
  3563. }
  3564.  
  3565. /**
  3566.  * Moves caret to matching opening or closing tag
  3567.  * @param {zen_editor} editor
  3568.  */
  3569. function goToMatchingPair(editor) {
  3570.     var content = String(editor.getContent()),
  3571.         caret_pos = editor.getCaretPos();
  3572.     
  3573.     if (content.charAt(caret_pos) == '<') 
  3574.         // looks like caret is outside of tag pair  
  3575.         caret_pos++;
  3576.         
  3577.     var tags = zen_coding.html_matcher.getTags(content, caret_pos, String(editor.getProfileName()));
  3578.         
  3579.     if (tags && tags[0]) {
  3580.         // match found
  3581.         var open_tag = tags[0],
  3582.             close_tag = tags[1];
  3583.             
  3584.         if (close_tag) { // exclude unary tags
  3585.             if (open_tag.start <= caret_pos && open_tag.end >= caret_pos)
  3586.                 editor.setCaretPos(close_tag.start);
  3587.             else if (close_tag.start <= caret_pos && close_tag.end >= caret_pos)
  3588.                 editor.setCaretPos(open_tag.start);
  3589.         }
  3590.     }
  3591. }
  3592.  
  3593. /**
  3594.  * Merge lines spanned by user selection. If there's no selection, tries to find
  3595.  * matching tags and use them as selection
  3596.  * @param {zen_editor} editor
  3597.  */
  3598. function mergeLines(editor) {
  3599.     var selection = editor.getSelectionRange();
  3600.     if (selection.start == selection.end) {
  3601.         // find matching tag
  3602.         var pair = zen_coding.html_matcher(String(editor.getContent()), editor.getCaretPos(), String(editor.getProfileName()));
  3603.         if (pair) {
  3604.             selection.start = pair[0];
  3605.             selection.end = pair[1];
  3606.         }
  3607.     }
  3608.     
  3609.     if (selection.start != selection.end) {
  3610.         // got range, merge lines
  3611.         var text = String(editor.getContent()).substring(selection.start, selection.end),
  3612.             old_length = text.length;
  3613.         var lines =  zen_coding.splitByLines(text);
  3614.         
  3615.         for (var i = 1; i < lines.length; i++) {
  3616.             lines[i] = lines[i].replace(/^\s+/, '');
  3617.         }
  3618.         
  3619.         text = lines.join('').replace(/\s{2,}/, ' ');
  3620.         editor.replaceContent(text, selection.start, selection.end);
  3621.         editor.createSelection(selection.start, selection.start + text.length);
  3622.     }
  3623. }
  3624.  
  3625. /**
  3626.  * Toggle comment on current editor's selection or HTML tag/CSS rule
  3627.  * @param {zen_editor} editor
  3628.  */
  3629. function toggleComment(editor) {
  3630.     var syntax = String(editor.getSyntax());
  3631.     if (syntax == 'css') {
  3632.         // in case out editor is good enough and can recognize syntax from 
  3633.         // current token, we have to make sure that cursor is not inside
  3634.         // 'style' attribute of html element
  3635.         var caret_pos = editor.getCaretPos();
  3636.         var pair = zen_coding.html_matcher.getTags(String(editor.getContent()), caret_pos);
  3637.         if (pair && pair[0] && pair[0].type == 'tag' && 
  3638.                 pair[0].start <= caret_pos && pair[0].end >= caret_pos) {
  3639.             syntax = 'html';
  3640.         }
  3641.     }
  3642.     
  3643.     switch (syntax) {
  3644.         case 'css':
  3645.             return toggleCSSComment(editor);
  3646.         default:
  3647.             return toggleHTMLComment(editor);
  3648.     }
  3649. }
  3650.  
  3651. /**
  3652.  * Toggle HTML comment on current selection or tag
  3653.  * @param {zen_editor} editor
  3654.  * @return {Boolean} Returns <code>true</code> if comment was toggled
  3655.  */
  3656. function toggleHTMLComment(editor) {
  3657.     var rng = editor.getSelectionRange(),
  3658.         content = String(editor.getContent());
  3659.         
  3660.     if (rng.start == rng.end) {
  3661.         // no selection, find matching tag
  3662.         var pair = zen_coding.html_matcher.getTags(content, editor.getCaretPos(), String(editor.getProfileName()));
  3663.         if (pair && pair[0]) { // found pair
  3664.             rng.start = pair[0].start;
  3665.             rng.end = pair[1] ? pair[1].end : pair[0].end;
  3666.         }
  3667.     }
  3668.     
  3669.     return genericCommentToggle(editor, '<!--', '-->', rng.start, rng.end);
  3670. }
  3671.  
  3672. /**
  3673.  * Simple CSS commenting
  3674.  * @param {zen_editor} editor
  3675.  * @return {Boolean} Returns <code>true</code> if comment was toggled
  3676.  */
  3677. function toggleCSSComment(editor) {
  3678.     var rng = editor.getSelectionRange();
  3679.         
  3680.     if (rng.start == rng.end) {
  3681.         // no selection, get current line
  3682.         rng = editor.getCurrentLineRange();
  3683.  
  3684.         // adjust start index till first non-space character
  3685.         var _r = narrowToNonSpace(String(editor.getContent()), rng.start, rng.end);
  3686.         rng.start = _r[0];
  3687.         rng.end = _r[1];
  3688.     }
  3689.     
  3690.     return genericCommentToggle(editor, '/*', '*/', rng.start, rng.end);
  3691. }
  3692.  
  3693. /**
  3694.  * Search for nearest comment in <code>str</code>, starting from index <code>from</code>
  3695.  * @param {String} text Where to search
  3696.  * @param {Number} from Search start index
  3697.  * @param {String} start_token Comment start string
  3698.  * @param {String} end_token Comment end string
  3699.  * @return {Array|null} Returns null if comment wasn't found
  3700.  */
  3701. function searchComment(text, from, start_token, end_token) {
  3702.     var start_ch = start_token.charAt(0),
  3703.         end_ch = end_token.charAt(0),
  3704.         comment_start = -1,
  3705.         comment_end = -1;
  3706.     
  3707.     function hasMatch(str, start) {
  3708.         return text.substr(start, str.length) == str;
  3709.     }
  3710.         
  3711.     // search for comment start
  3712.     while (from--) {
  3713.         if (text.charAt(from) == start_ch && hasMatch(start_token, from)) {
  3714.             comment_start = from;
  3715.             break;
  3716.         }
  3717.     }
  3718.     
  3719.     if (comment_start != -1) {
  3720.         // search for comment end
  3721.         from = comment_start;
  3722.         var content_len = text.length;
  3723.         while (content_len >= from++) {
  3724.             if (text.charAt(from) == end_ch && hasMatch(end_token, from)) {
  3725.                 comment_end = from + end_token.length;
  3726.                 break;
  3727.             }
  3728.         }
  3729.     }
  3730.     
  3731.     return (comment_start != -1 && comment_end != -1) 
  3732.         ? [comment_start, comment_end] 
  3733.         : null;
  3734. }
  3735.  
  3736. /**
  3737.  * Escape special regexp chars in string, making it usable for creating dynamic
  3738.  * regular expressions
  3739.  * @param {String} str
  3740.  * @return {String}
  3741.  */
  3742. function escapeForRegexp(str) {
  3743.   var specials = new RegExp("[.*+?|()\\[\\]{}\\\\]", "g"); // .*+?|()[]{}\
  3744.   return str.replace(specials, "\\$&");
  3745. }
  3746.  
  3747. /**
  3748.  * Generic comment toggling routine
  3749.  * @param {zen_editor} editor
  3750.  * @param {String} comment_start Comment start token
  3751.  * @param {String} comment_end Comment end token
  3752.  * @param {Number} range_start Start selection range
  3753.  * @param {Number} range_end End selection range
  3754.  * @return {Boolean}
  3755.  */
  3756. function genericCommentToggle(editor, comment_start, comment_end, range_start, range_end) {
  3757.     var content = String(editor.getContent()),
  3758.         caret_pos = editor.getCaretPos(),
  3759.         new_content = null;
  3760.         
  3761.     /**
  3762.      * Remove comment markers from string
  3763.      * @param {Sting} str
  3764.      * @return {String}
  3765.      */
  3766.     function removeComment(str) {
  3767.         return str
  3768.             .replace(new RegExp('^' + escapeForRegexp(comment_start) + '\\s*'), function(str){
  3769.                 caret_pos -= str.length;
  3770.                 return '';
  3771.             }).replace(new RegExp('\\s*' + escapeForRegexp(comment_end) + '$'), '');
  3772.     }
  3773.     
  3774.     function hasMatch(str, start) {
  3775.         return content.substr(start, str.length) == str;
  3776.     }
  3777.         
  3778.     // first, we need to make sure that this substring is not inside 
  3779.     // comment
  3780.     var comment_range = searchComment(content, caret_pos, comment_start, comment_end);
  3781.     
  3782.     if (comment_range && comment_range[0] <= range_start && comment_range[1] >= range_end) {
  3783.         // we're inside comment, remove it
  3784.         range_start = comment_range[0];
  3785.         range_end = comment_range[1];
  3786.         
  3787.         new_content = removeComment(content.substring(range_start, range_end));
  3788.     } else {
  3789.         // should add comment
  3790.         // make sure that there's no comment inside selection
  3791.         new_content = comment_start + ' ' + 
  3792.             content.substring(range_start, range_end)
  3793.                 .replace(new RegExp(escapeForRegexp(comment_start) + '\\s*|\\s*' + escapeForRegexp(comment_end), 'g'), '') +
  3794.             ' ' + comment_end;
  3795.             
  3796.         // adjust caret position
  3797.         caret_pos += comment_start.length + 1;
  3798.     }
  3799.  
  3800.     // replace editor content
  3801.     if (new_content !== null) {
  3802.         editor.setCaretPos(range_start);
  3803.         editor.replaceContent(unindent(editor, new_content), range_start, range_end);
  3804.         editor.setCaretPos(caret_pos);
  3805.         return true;
  3806.     }
  3807.     
  3808.     return false;
  3809. }
  3810.  
  3811. /**
  3812.  * Splits or joins tag, e.g. transforms it into a short notation and vice versa:<br>
  3813.  * <div></div> ΓåÆ <div /> : join<br>
  3814.  * <div /> ΓåÆ <div></div> : split
  3815.  * @param {zen_editor} editor Editor instance
  3816.  * @param {String} [profile_name] Profile name
  3817.  */
  3818. function splitJoinTag(editor, profile_name) {
  3819.     var caret_pos = editor.getCaretPos(),
  3820.         profile = zen_coding.getProfile(String(profile_name || editor.getProfileName())),
  3821.         caret = zen_coding.getCaretPlaceholder();
  3822.  
  3823.     // find tag at current position
  3824.     var pair = zen_coding.html_matcher.getTags(String(editor.getContent()), caret_pos, String(editor.getProfileName()));
  3825.     if (pair && pair[0]) {
  3826.         var new_content = pair[0].full_tag;
  3827.         
  3828.         if (pair[1]) { // join tag
  3829.             var closing_slash = ' /';
  3830.             if (profile.self_closing_tag === true)
  3831.                 closing_slash = '/';
  3832.                 
  3833.             new_content = new_content.replace(/\s*>$/, closing_slash + '>');
  3834.             
  3835.             // add caret placeholder
  3836.             if (new_content.length + pair[0].start < caret_pos)
  3837.                 new_content += caret;
  3838.             else {
  3839.                 var d = caret_pos - pair[0].start;
  3840.                 new_content = new_content.substring(0, d) + caret + new_content.substring(d);
  3841.             }
  3842.             
  3843.             editor.replaceContent(new_content, pair[0].start, pair[1].end);
  3844.         } else { // split tag
  3845.             var nl = zen_coding.getNewline(),
  3846.                 pad = zen_coding.getVariable('indentation');
  3847.             
  3848.             // define tag content depending on profile
  3849.             var tag_content = (profile.tag_nl === true)
  3850.                     ? nl + pad +caret + nl
  3851.                     : caret;
  3852.                     
  3853.             new_content = new_content.replace(/\s*\/>$/, '>') + tag_content + '</' + pair[0].name + '>';
  3854.             editor.replaceContent(new_content, pair[0].start, pair[0].end);
  3855.         }
  3856.         
  3857.         return true;
  3858.     } else {
  3859.         return false;
  3860.     }
  3861. }
  3862.  
  3863. /**
  3864.  * Returns line bounds for specific character position
  3865.  * @param {String} text
  3866.  * @param {Number} from Where to start searching
  3867.  * @return {Object}
  3868.  */
  3869. function getLineBounds(text, from) {
  3870.     var len = text.length,
  3871.         start = 0,
  3872.         end = len - 1;
  3873.     
  3874.     // search left
  3875.     for (var i = from - 1; i > 0; i--) {
  3876.         var ch = text.charAt(i);
  3877.         if (ch == '\n' || ch == '\r') {
  3878.             start = i + 1;
  3879.             break;
  3880.         }
  3881.     }
  3882.     // search right
  3883.     for (var j = from; j < len; j++) {
  3884.         var ch = text.charAt(j);
  3885.         if (ch == '\n' || ch == '\r') {
  3886.             end = j;
  3887.             break;
  3888.         }
  3889.     }
  3890.     
  3891.     return {start: start, end: end};
  3892. }
  3893.  
  3894. /**
  3895.  * Gracefully removes tag under cursor
  3896.  * @param {zen_editor} editor
  3897.  */
  3898. function removeTag(editor) {
  3899.     var caret_pos = editor.getCaretPos(),
  3900.         content = String(editor.getContent());
  3901.         
  3902.     // search for tag
  3903.     var pair = zen_coding.html_matcher.getTags(content, caret_pos, String(editor.getProfileName()));
  3904.     if (pair && pair[0]) {
  3905.         if (!pair[1]) {
  3906.             // simply remove unary tag
  3907.             editor.replaceContent(zen_coding.getCaretPlaceholder(), pair[0].start, pair[0].end);
  3908.         } else {
  3909.             var tag_content_range = narrowToNonSpace(content, pair[0].end, pair[1].start),
  3910.                 start_line_bounds = getLineBounds(content, tag_content_range[0]),
  3911.                 start_line_pad = getLinePadding(content.substring(start_line_bounds.start, start_line_bounds.end)),
  3912.                 tag_content = content.substring(tag_content_range[0], tag_content_range[1]);
  3913.                 
  3914.             tag_content = unindentText(tag_content, start_line_pad);
  3915.             editor.replaceContent(zen_coding.getCaretPlaceholder() + tag_content, pair[0].start, pair[1].end);
  3916.         }
  3917.         
  3918.         return true;
  3919.     } else {
  3920.         return false;
  3921.     }
  3922. }
  3923.  
  3924. /**
  3925.  * Test if <code>text</code> starts with <code>token</code> at <code>pos</code>
  3926.  * position. If <code>pos</code> is ommited, search from beginning of text 
  3927.  * @param {String} token Token to test
  3928.  * @param {String} text Where to search
  3929.  * @param {Number} pos Position where to start search
  3930.  * @return {Boolean}
  3931.  * @since 0.65
  3932.  */
  3933. function startsWith(token, text, pos) {
  3934.     pos = pos || 0;
  3935.     return text.charAt(pos) == token.charAt(0) && text.substr(pos, token.length) == token;
  3936. }
  3937.  
  3938. /**
  3939.  * Encodes/decodes image under cursor to/from base64
  3940.  * @param {zen_editor} editor
  3941.  * @since 0.65
  3942.  */
  3943. function encodeDecodeBase64(editor) {
  3944.     var data = String(editor.getSelection()),
  3945.         caret_pos = editor.getCaretPos();
  3946.         
  3947.     if (!data) {
  3948.         // no selection, try to find image bounds from current caret position
  3949.         var text = String(editor.getContent()),
  3950.             ch, 
  3951.             m;
  3952.         while (caret_pos-- >= 0) {
  3953.             if (startsWith('src=', text, caret_pos)) { // found <img src="">
  3954.                 if (m = text.substr(caret_pos).match(/^(src=(["'])?)([^'"<>\s]+)\1?/)) {
  3955.                     data = m[3];
  3956.                     caret_pos += m[1].length;
  3957.                 }
  3958.                 break;
  3959.             } else if (startsWith('url(', text, caret_pos)) { // found CSS url() pattern
  3960.                 if (m = text.substr(caret_pos).match(/^(url\((['"])?)([^'"\)\s]+)\1?/)) {
  3961.                     data = m[3];
  3962.                     caret_pos += m[1].length;
  3963.                 }
  3964.                 break;
  3965.             }
  3966.         }
  3967.     }
  3968.     
  3969.     if (data) {
  3970.         if (startsWith('data:', data))
  3971.             return decodeFromBase64(editor, data, caret_pos);
  3972.         else
  3973.             return encodeToBase64(editor, data, caret_pos);
  3974.     } else {
  3975.         return false;
  3976.     }
  3977. }
  3978.  
  3979. /**
  3980.  * Encodes image to base64
  3981.  * @requires zen_file
  3982.  * 
  3983.  * @param {zen_editor} editor
  3984.  * @param {String} img_path Path to image
  3985.  * @param {Number} pos Caret position where image is located in the editor
  3986.  * @return {Boolean}
  3987.  */
  3988. function encodeToBase64(editor, img_path, pos) {
  3989.     var editor_file = editor.getFilePath(),
  3990.         default_mime_type = 'application/octet-stream';
  3991.         
  3992.     if (editor_file === null) {
  3993.         throw "You should save your file before using this action";
  3994.     }
  3995.     
  3996.     // locate real image path
  3997.     var real_img_path = zen_file.locateFile(editor_file, img_path);
  3998.     if (real_img_path === null) {
  3999.         throw "Can't find " + img_path + ' file';
  4000.     }
  4001.     
  4002.     var b64 = base64.encode(String(zen_file.read(real_img_path)));
  4003.     if (!b64) {
  4004.         throw "Can't encode file content to base64";
  4005.     }
  4006.     
  4007.     b64 = 'data:' + (base64.mime_types[String(zen_file.getExt(real_img_path))] || default_mime_type) +
  4008.         ';base64,' + b64;
  4009.         
  4010.     editor.replaceContent('$0' + b64, pos, pos + img_path.length);
  4011.     return true;
  4012. }
  4013.  
  4014. /**
  4015.  * Decodes base64 string back to file.
  4016.  * @requires zen_editor.prompt
  4017.  * @requires zen_file
  4018.  * 
  4019.  * @param {zen_editor} editor
  4020.  * @param {String} data Base64-encoded file content
  4021.  * @param {Number} pos Caret position where image is located in the editor
  4022.  */
  4023. function decodeFromBase64(editor, data, pos) {
  4024.     // ask user to enter path to file
  4025.     var file_path = String(editor.prompt('Enter path to file (absolute or relative)'));
  4026.     if (!file_path)
  4027.         return false;
  4028.         
  4029.     var abs_path = zen_file.createPath(editor.getFilePath(), file_path);
  4030.     if (!abs_path) {
  4031.         throw "Can't save file";
  4032.     }
  4033.     
  4034.     zen_file.save(abs_path, base64.decode( data.replace(/^data\:.+?;.+?,/, '') ));
  4035.     editor.replaceContent('$0' + file_path, pos, pos + data.length);
  4036.     return true;
  4037. }
  4038.  
  4039. /**
  4040.  * Make decimal number look good: convert it to fixed precision end remove
  4041.  * traling zeroes 
  4042.  * @param {Number} num
  4043.  * @param {Number} [fracion] Fraction numbers (default is 2)
  4044.  * @return {String}
  4045.  */
  4046. function prettifyNumber(num, fraction) {
  4047.     return num.toFixed(typeof fraction == 'undefined' ? 2 : fraction).replace(/\.?0+$/, '');
  4048. }
  4049.  
  4050. /**
  4051.  * Find expression bounds in current editor at caret position. 
  4052.  * On each character a <code>fn</code> function will be caller which must 
  4053.  * return <code>true</code> if current character meets requirements, 
  4054.  * <code>false</code> otherwise
  4055.  * @param {zen_editor} editor
  4056.  * @param {Function} fn Function to test each character of expression
  4057.  * @return {Array} If expression found, returns array with start and end 
  4058.  * positions 
  4059.  */
  4060. function findExpressionBounds(editor, fn) {
  4061.     var content = String(editor.getContent()),
  4062.         il = content.length,
  4063.         expr_start = editor.getCaretPos() - 1,
  4064.         expr_end = expr_start + 1;
  4065.         
  4066.     // start by searching left
  4067.     while (expr_start >= 0 && fn(content.charAt(expr_start), expr_start, content)) expr_start--;
  4068.     
  4069.     // then search right
  4070.     while (expr_end < il && fn(content.charAt(expr_end), expr_end, content)) expr_end++;
  4071.     
  4072.     return expr_end > expr_start ? [++expr_start, expr_end] : null;
  4073. }
  4074.  
  4075. /**
  4076.  * Extract number from current caret position of the <code>editor</code> and
  4077.  * increment it by <code>step</code>
  4078.  * @param {zen_editor} editor
  4079.  * @param {Number} step Increment step (may be negative)
  4080.  */
  4081. function incrementNumber(editor, step) {
  4082.     var content = String(editor.getContent()),
  4083.         has_sign = false,
  4084.         has_decimal = false;
  4085.         
  4086.     var r = findExpressionBounds(editor, function(ch) {
  4087.         if (zen_coding.isNumeric(ch))
  4088.             return true;
  4089.         if (ch == '.')
  4090.             return has_decimal ? false : has_decimal = true;
  4091.         if (ch == '-')
  4092.             return has_sign ? false : has_sign = true;
  4093.             
  4094.         return false;
  4095.     });
  4096.         
  4097.     if (r) {
  4098.         var num = parseFloat(content.substring(r[0], r[1]));
  4099.         if (!isNaN(num)) {
  4100.             num = prettifyNumber(num + step);
  4101.             editor.replaceContent(num, r[0], r[1]);
  4102.             editor.createSelection(r[0], r[0] + num.length);
  4103.             return true;
  4104.         }
  4105.     }
  4106.     
  4107.     return false;
  4108. }
  4109.  
  4110. /**
  4111.  * Evaluates simple math expresison under caret
  4112.  * @param {zen_editor} editor
  4113.  */
  4114. function evaluateMathExpression(editor) {
  4115.     var content = String(editor.getContent()),
  4116.         chars = '.+-*/\\';
  4117.         
  4118.     var r = findExpressionBounds(editor, function(ch) {
  4119.         return zen_coding.isNumeric(ch) || chars.indexOf(ch) != -1;
  4120.     });
  4121.         
  4122.     if (r) {
  4123.         var expr = content.substring(r[0], r[1]);
  4124.         
  4125.         // replace integral division: 11\2 => Math.round(11/2) 
  4126.         expr = expr.replace(/([\d\.\-]+)\\([\d\.\-]+)/g, 'Math.round($1/$2)');
  4127.         
  4128.         try {
  4129.             var result = new Function('return ' + expr)();
  4130.             result = prettifyNumber(result);
  4131.             editor.replaceContent(result, r[0], r[1]);
  4132.             editor.setCaretPos(r[0] + result.length);
  4133.             return true;
  4134.         } catch (e) {}
  4135.     }
  4136.     
  4137.     return false;
  4138. }
  4139.  
  4140. // register all actions
  4141. zen_coding.registerAction('expand_abbreviation', expandAbbreviation);
  4142. zen_coding.registerAction('expand_abbreviation_with_tab', expandAbbreviationWithTab);
  4143. zen_coding.registerAction('match_pair', matchPair);
  4144. zen_coding.registerAction('match_pair_inward', function(editor){
  4145.     matchPair(editor, 'in');
  4146. });
  4147.  
  4148. zen_coding.registerAction('match_pair_outward', function(editor){
  4149.     matchPair(editor, 'out');
  4150. });
  4151. zen_coding.registerAction('wrap_with_abbreviation', wrapWithAbbreviation);
  4152. zen_coding.registerAction('prev_edit_point', prevEditPoint);
  4153. zen_coding.registerAction('next_edit_point', nextEditPoint);
  4154. zen_coding.registerAction('insert_formatted_line_break', insertFormattedNewline);
  4155. zen_coding.registerAction('insert_formatted_line_break_only', insertFormattedNewlineOnly);
  4156. zen_coding.registerAction('select_line', selectLine);
  4157. zen_coding.registerAction('matching_pair', goToMatchingPair);
  4158. zen_coding.registerAction('merge_lines', mergeLines);
  4159. zen_coding.registerAction('toggle_comment', toggleComment);
  4160. zen_coding.registerAction('split_join_tag', splitJoinTag);
  4161. zen_coding.registerAction('remove_tag', removeTag);
  4162. zen_coding.registerAction('encode_decode_data_url', encodeDecodeBase64);
  4163. //zen_coding.registerAction('update_image_size', updateImageSize);
  4164.  
  4165. zen_coding.registerAction('increment_number_by_1', function(editor) {
  4166.     return incrementNumber(editor, 1);
  4167. });
  4168.  
  4169. zen_coding.registerAction('decrement_number_by_1', function(editor) {
  4170.     return incrementNumber(editor, -1);
  4171. });
  4172.  
  4173. zen_coding.registerAction('increment_number_by_10', function(editor) {
  4174.     return incrementNumber(editor, 10);
  4175. });
  4176.  
  4177. zen_coding.registerAction('decrement_number_by_10', function(editor) {
  4178.     return incrementNumber(editor, -10);
  4179. });
  4180.  
  4181. zen_coding.registerAction('increment_number_by_01', function(editor) {
  4182.     return incrementNumber(editor, 0.1);
  4183. });
  4184.  
  4185. zen_coding.registerAction('decrement_number_by_01', function(editor) {
  4186.     return incrementNumber(editor, -0.1);
  4187. });
  4188.  
  4189. zen_coding.registerAction('evaluate_math_expression', evaluateMathExpression);
  4190. /**
  4191.  * @author Sergey Chikuyonok (serge.che@gmail.com)
  4192.  * @link http://chikuyonok.ru
  4193.  */
  4194. (function(){
  4195.     // Regular Expressions for parsing tags and attributes
  4196.     var start_tag = /^<([\w\:\-]+)((?:\s+[\w\-:]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/,
  4197.         end_tag = /^<\/([\w\:\-]+)[^>]*>/,
  4198.         attr = /([\w\-:]+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g;
  4199.         
  4200.     // Empty Elements - HTML 4.01
  4201.     var empty = makeMap("area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed");
  4202.  
  4203.     // Block Elements - HTML 4.01
  4204.     var block = makeMap("address,applet,blockquote,button,center,dd,dir,div,dl,dt,fieldset,form,frameset,hr,iframe,isindex,li,map,menu,noframes,noscript,object,ol,p,pre,script,table,tbody,td,tfoot,th,thead,tr,ul");
  4205.  
  4206.     // Inline Elements - HTML 4.01
  4207.     var inline = makeMap("a,abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,code,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,select,small,span,strike,strong,sub,sup,textarea,tt,u,var");
  4208.  
  4209.     // Elements that you can, intentionally, leave open
  4210.     // (and which close themselves)
  4211.     var close_self = makeMap("colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr");
  4212.     
  4213.     /** Current matching mode */
  4214.     var cur_mode = 'xhtml';
  4215.     
  4216.     /** Last matched HTML pair */
  4217.     var last_match = {
  4218.         opening_tag: null, // tag() or comment() object
  4219.         closing_tag: null, // tag() or comment() object
  4220.         start_ix: -1,
  4221.         end_ix: -1
  4222.     };
  4223.     
  4224.     function setMode(new_mode) {
  4225.         if (!new_mode || new_mode != 'html')
  4226.             new_mode = 'xhtml';
  4227.             
  4228.         cur_mode = new_mode;
  4229.     }
  4230.     
  4231.     function tag(match, ix) {
  4232.         var name = match[1].toLowerCase();
  4233.         return  {
  4234.             name: name,
  4235.             full_tag: match[0],
  4236.             start: ix,
  4237.             end: ix + match[0].length,
  4238.             unary: Boolean(match[3]) || (name in empty && cur_mode == 'html'),
  4239.             has_close: Boolean(match[3]),
  4240.             type: 'tag',
  4241.             close_self: (name in close_self && cur_mode == 'html')
  4242.         };
  4243.     }
  4244.     
  4245.     function comment(start, end) {
  4246.         return {
  4247.             start: start,
  4248.             end: end,
  4249.             type: 'comment'
  4250.         };
  4251.     }
  4252.     
  4253.     function makeMap(str){
  4254.         var obj = {}, items = str.split(",");
  4255.         for ( var i = 0; i < items.length; i++ )
  4256.             obj[ items[i] ] = true;
  4257.         return obj;
  4258.     }
  4259.     
  4260.     /**
  4261.      * Makes selection ranges for matched tag pair
  4262.      * @param {tag} opening_tag
  4263.      * @param {tag} closing_tag
  4264.      * @param {Number} ix
  4265.      */
  4266.     function makeRange(opening_tag, closing_tag, ix) {
  4267.         ix = ix || 0;
  4268.         
  4269.         var start_ix = -1, 
  4270.             end_ix = -1;
  4271.         
  4272.         if (opening_tag && !closing_tag) { // unary element
  4273.             start_ix = opening_tag.start;
  4274.             end_ix = opening_tag.end;
  4275.         } else if (opening_tag && closing_tag) { // complete element
  4276.             if (
  4277.                 (opening_tag.start < ix && opening_tag.end > ix) || 
  4278.                 (closing_tag.start <= ix && closing_tag.end > ix)
  4279.             ) {
  4280.                 start_ix = opening_tag.start;
  4281.                 end_ix = closing_tag.end;
  4282.             } else {
  4283.                 start_ix = opening_tag.end;
  4284.                 end_ix = closing_tag.start;
  4285.             }
  4286.         }
  4287.         
  4288.         return [start_ix, end_ix];
  4289.     }
  4290.     
  4291.     /**
  4292.      * Save matched tag for later use and return found indexes
  4293.      * @param {tag} opening_tag
  4294.      * @param {tag} closing_tag
  4295.      * @param {Number} ix
  4296.      * @return {Array}
  4297.      */
  4298.     function saveMatch(opening_tag, closing_tag, ix) {
  4299.         ix = ix || 0;
  4300.         last_match.opening_tag = opening_tag; 
  4301.         last_match.closing_tag = closing_tag;
  4302.         
  4303.         var range = makeRange(opening_tag, closing_tag, ix);
  4304.         last_match.start_ix = range[0];
  4305.         last_match.end_ix = range[1];
  4306.         
  4307.         return last_match.start_ix != -1 ? [last_match.start_ix, last_match.end_ix] : null;
  4308.     }
  4309.     
  4310.     /**
  4311.      * Handle unary tag: find closing tag if needed
  4312.      * @param {String} text
  4313.      * @param {Number} ix
  4314.      * @param {tag} open_tag
  4315.      * @return {tag|null} Closing tag (or null if not found) 
  4316.      */
  4317.     function handleUnaryTag(text, ix, open_tag) {
  4318.         if (open_tag.has_close)
  4319.             return null;
  4320.         else {
  4321.             // TODO finish this method
  4322.         }
  4323.     }
  4324.     
  4325.     /**
  4326.      * Search for matching tags in <code>html</code>, starting from 
  4327.      * <code>start_ix</code> position
  4328.      * @param {String} html Code to search
  4329.      * @param {Number} start_ix Character index where to start searching pair 
  4330.      * (commonly, current caret position)
  4331.      * @param {Function} action Function that creates selection range
  4332.      * @return {Array|null}
  4333.      */
  4334.     function findPair(html, start_ix, mode, action) {
  4335.         action = action || makeRange;
  4336.         setMode(mode);
  4337.         
  4338.         var forward_stack = [],
  4339.             backward_stack = [],
  4340.             /** @type {tag()} */
  4341.             opening_tag = null,
  4342.             /** @type {tag()} */
  4343.             closing_tag = null,
  4344.             range = null,
  4345.             html_len = html.length,
  4346.             m,
  4347.             ix,
  4348.             tmp_tag;
  4349.             
  4350.         forward_stack.last = backward_stack.last = function() {
  4351.             return this[this.length - 1];
  4352.         }
  4353.         
  4354.         function hasMatch(str, start) {
  4355.             if (arguments.length == 1)
  4356.                 start = ix;
  4357.             return html.substr(start, str.length) == str;
  4358.         }
  4359.         
  4360.         function searchCommentStart(from) {
  4361.             while (from--) {
  4362.                 if (html.charAt(from) == '<' && hasMatch('<!--', from))
  4363.                     break;
  4364.             }
  4365.             
  4366.             return from;
  4367.         }
  4368.         
  4369.         // find opening tag
  4370.         ix = start_ix;
  4371.         while (ix-- && ix >= 0) {
  4372.             var ch = html.charAt(ix);
  4373.             if (ch == '<') {
  4374.                 var check_str = html.substring(ix, html_len);
  4375.                 
  4376.                 if ( (m = check_str.match(end_tag)) ) { // found closing tag
  4377.                     tmp_tag = tag(m, ix);
  4378.                     if (tmp_tag.start < start_ix && tmp_tag.end > start_ix) // direct hit on searched closing tag
  4379.                         closing_tag = tmp_tag;
  4380.                     else
  4381.                         backward_stack.push(tmp_tag);
  4382.                 } else if ( (m = check_str.match(start_tag)) ) { // found opening tag
  4383.                     tmp_tag = tag(m, ix);
  4384.                     
  4385.                     if (tmp_tag.unary) {
  4386.                         if (tmp_tag.start < start_ix && tmp_tag.end > start_ix) // exact match
  4387.                             // TODO handle unary tag 
  4388.                             return action(tmp_tag, null, start_ix);
  4389.                     } else if (backward_stack.last() && backward_stack.last().name == tmp_tag.name) {
  4390.                         backward_stack.pop();
  4391.                     } else { // found nearest unclosed tag
  4392.                         opening_tag = tmp_tag;
  4393.                         break;
  4394.                     }
  4395.                 } else if (check_str.indexOf('<!--') == 0) { // found comment start
  4396.                     var end_ix = check_str.search('-->') + ix + 3;
  4397.                     if (ix < start_ix && end_ix >= start_ix)
  4398.                         return action( comment(ix, end_ix) );
  4399.                 }
  4400.             } else if (ch == '-' && hasMatch('-->')) { // found comment end
  4401.                 // search left until comment start is reached
  4402.                 ix = searchCommentStart(ix);
  4403.             }
  4404.         }
  4405.         
  4406.         if (!opening_tag)
  4407.             return action(null);
  4408.         
  4409.         // find closing tag
  4410.         if (!closing_tag) {
  4411.             for (ix = start_ix; ix < html_len; ix++) {
  4412.                 var ch = html.charAt(ix);
  4413.                 if (ch == '<') {
  4414.                     var check_str = html.substring(ix, html_len);
  4415.                     
  4416.                     if ( (m = check_str.match(start_tag)) ) { // found opening tag
  4417.                         tmp_tag = tag(m, ix);
  4418.                         if (!tmp_tag.unary)
  4419.                             forward_stack.push( tmp_tag );
  4420.                     } else if ( (m = check_str.match(end_tag)) ) { // found closing tag
  4421.                         var tmp_tag = tag(m, ix);
  4422.                         if (forward_stack.last() && forward_stack.last().name == tmp_tag.name)
  4423.                             forward_stack.pop();
  4424.                         else { // found matched closing tag
  4425.                             closing_tag = tmp_tag;
  4426.                             break;
  4427.                         }
  4428.                     } else if (hasMatch('<!--')) { // found comment
  4429.                         ix += check_str.search('-->') + 2;
  4430.                     }
  4431.                 } else if (ch == '-' && hasMatch('-->')) {
  4432.                     // looks like cursor was inside comment with invalid HTML
  4433.                     if (!forward_stack.last() || forward_stack.last().type != 'comment') {
  4434.                         var end_ix = ix + 3;
  4435.                         return action(comment( searchCommentStart(ix), end_ix ));
  4436.                     }
  4437.                 }
  4438.             }
  4439.         }
  4440.         
  4441.         return action(opening_tag, closing_tag, start_ix);
  4442.     }
  4443.     
  4444.     /**
  4445.      * Search for matching tags in <code>html</code>, starting 
  4446.      * from <code>start_ix</code> position. The result is automatically saved in 
  4447.      * <code>last_match</code> property
  4448.      * 
  4449.      * @return {Array|null}
  4450.      */
  4451.     var HTMLPairMatcher = function(/* String */ html, /* Number */ start_ix, /*  */ mode){
  4452.         return findPair(html, start_ix, mode, saveMatch);
  4453.     }
  4454.     
  4455.     HTMLPairMatcher.start_tag = start_tag;
  4456.     HTMLPairMatcher.end_tag = end_tag;
  4457.     
  4458.     /**
  4459.      * Search for matching tags in <code>html</code>, starting from 
  4460.      * <code>start_ix</code> position. The difference between 
  4461.      * <code>HTMLPairMatcher</code> function itself is that <code>find</code> 
  4462.      * method doesn't save matched result in <code>last_match</code> property.
  4463.      * This method is generally used for lookups 
  4464.      */
  4465.     HTMLPairMatcher.find = function(html, start_ix, mode) {
  4466.         return findPair(html, start_ix, mode);
  4467.     };
  4468.     
  4469.     /**
  4470.      * Search for matching tags in <code>html</code>, starting from 
  4471.      * <code>start_ix</code> position. The difference between 
  4472.      * <code>HTMLPairMatcher</code> function itself is that <code>getTags</code> 
  4473.      * method doesn't save matched result in <code>last_match</code> property 
  4474.      * and returns array of opening and closing tags
  4475.      * This method is generally used for lookups 
  4476.      */
  4477.     HTMLPairMatcher.getTags = function(html, start_ix, mode) {
  4478.         return findPair(html, start_ix, mode, function(opening_tag, closing_tag){
  4479.             return [opening_tag, closing_tag];
  4480.         });
  4481.     };
  4482.     
  4483.     HTMLPairMatcher.last_match = last_match;
  4484.     
  4485.     try {
  4486.         zen_coding.html_matcher = HTMLPairMatcher;
  4487.     } catch(e){}
  4488.     
  4489. })();/**
  4490.  * @author Sergey Chikuyonok (serge.che@gmail.com)
  4491.  * @link http://chikuyonok.ru
  4492.  */
  4493. var base64 = {
  4494.     chars : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
  4495.  
  4496.     mime_types : {
  4497.         'gif' : 'image/gif',
  4498.         'png' : 'image/png',
  4499.         'jpg' : 'image/jpeg',
  4500.         'jpeg' : 'image/jpeg',
  4501.         'svg' : 'image/svg+xml',
  4502.         'html' : 'text/html',
  4503.         'htm' : 'text/html'
  4504.     },
  4505.  
  4506.     encode : function(input) {
  4507.         var output = [];
  4508.         var chr1, chr2, chr3, enc1, enc2, enc3, enc4, cdp1, cdp2, cdp3;
  4509.         var i = 0, il = input.length, b64_str = this.chars;
  4510.  
  4511.         while (i < il) {
  4512.  
  4513.             cdp1 = input.charCodeAt(i++);
  4514.             cdp2 = input.charCodeAt(i++);
  4515.             cdp3 = input.charCodeAt(i++);
  4516.  
  4517.             chr1 = cdp1 & 0xff;
  4518.             chr2 = cdp2 & 0xff;
  4519.             chr3 = cdp3 & 0xff;
  4520.  
  4521.             enc1 = chr1 >> 2;
  4522.             enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
  4523.             enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
  4524.             enc4 = chr3 & 63;
  4525.  
  4526.             if (isNaN(cdp2)) {
  4527.                 enc3 = enc4 = 64;
  4528.             } else if (isNaN(cdp3)) {
  4529.                 enc4 = 64;
  4530.             }
  4531.  
  4532.             output.push(b64_str.charAt(enc1) + b64_str.charAt(enc2) + b64_str.charAt(enc3) + b64_str.charAt(enc4));
  4533.         }
  4534.  
  4535.         return output.join('');
  4536.     },
  4537.  
  4538.     /**
  4539.      * Decodes string using MIME base64 algorithm
  4540.      * 
  4541.      * @author Tyler Akins (http://rumkin.com)
  4542.      * @param {String}
  4543.      *            data
  4544.      * @return {String}
  4545.      */
  4546.     decode : function(data) {
  4547.         var o1, o2, o3, h1, h2, h3, h4, bits, i = 0, ac = 0, dec = "", tmp_arr = [];
  4548.         var b64 = this.chars, il = data.length;
  4549.  
  4550.         if (!data) {
  4551.             return data;
  4552.         }
  4553.  
  4554.         data += '';
  4555.  
  4556.         do { // unpack four hexets into three octets using index points in b64
  4557.             h1 = b64.indexOf(data.charAt(i++));
  4558.             h2 = b64.indexOf(data.charAt(i++));
  4559.             h3 = b64.indexOf(data.charAt(i++));
  4560.             h4 = b64.indexOf(data.charAt(i++));
  4561.  
  4562.             bits = h1 << 18 | h2 << 12 | h3 << 6 | h4;
  4563.  
  4564.             o1 = bits >> 16 & 0xff;
  4565.             o2 = bits >> 8 & 0xff;
  4566.             o3 = bits & 0xff;
  4567.  
  4568.             if (h3 == 64) {
  4569.                 tmp_arr[ac++] = String.fromCharCode(o1);
  4570.             } else if (h4 == 64) {
  4571.                 tmp_arr[ac++] = String.fromCharCode(o1, o2);
  4572.             } else {
  4573.                 tmp_arr[ac++] = String.fromCharCode(o1, o2, o3);
  4574.             }
  4575.         } while (i < il);
  4576.  
  4577.         return tmp_arr.join('');
  4578.     }
  4579. };/* This file defines an XML parser, with a few kludges to make it
  4580.  * useable for HTML. autoSelfClosers defines a set of tag names that
  4581.  * are expected to not have a closing tag, and doNotIndent specifies
  4582.  * the tags inside of which no indentation should happen (see Config
  4583.  * object). These can be disabled by passing the editor an object like
  4584.  * {useHTMLKludges: false} as parserConfig option.
  4585.  * 
  4586.  * Original code by Marijn Haverbeke
  4587.  * from CodeMirror projet: http://codemirror.net/
  4588.  */
  4589.  
  4590. var XMLParser = (function() {
  4591.     // The value used to signal the end of a sequence in iterators.
  4592.     var StopIteration = {
  4593.         toString : function() {
  4594.             return "StopIteration"
  4595.         }
  4596.     };
  4597.     
  4598.     // Apply a function to each element in a sequence.
  4599.     function forEach(iter, f) {
  4600.         if (iter.next) {
  4601.             try {
  4602.                 while (true)
  4603.                     f(iter.next());
  4604.             } catch (e) {
  4605.                 if (e != StopIteration)
  4606.                     throw e;
  4607.             }
  4608.         } else {
  4609.             for (var i = 0; i < iter.length; i++)
  4610.                 f(iter[i]);
  4611.         }
  4612.     }
  4613.     
  4614.     // A framework for simple tokenizers. Takes care of newlines and
  4615.     // white-space, and of getting the text from the source stream into
  4616.     // the token object. A state is a function of two arguments -- a
  4617.     // string stream and a setState function. The second can be used to
  4618.     // change the tokenizer's state, and can be ignored for stateless
  4619.     // tokenizers. This function should advance the stream over a token
  4620.     // and return a string or object containing information about the next
  4621.     // token, or null to pass and have the (new) state be called to finish
  4622.     // the token. When a string is given, it is wrapped in a {style, type}
  4623.     // object. In the resulting object, the characters consumed are stored
  4624.     // under the content property. Any whitespace following them is also
  4625.     // automatically consumed, and added to the value property. (Thus,
  4626.     // content is the actual meaningful part of the token, while value
  4627.     // contains all the text it spans.)
  4628.     
  4629.     function tokenizer(source, state) {
  4630.         // Newlines are always a separate token.
  4631.         function isWhiteSpace(ch) {
  4632.             // The messy regexp is because IE's regexp matcher is of the
  4633.             // opinion that non-breaking spaces are no whitespace.
  4634.             return ch != "\n" && /^[\s\u00a0]*$/.test(ch);
  4635.         }
  4636.     
  4637.         var tokenizer = {
  4638.             state : state,
  4639.     
  4640.             take : function(type) {
  4641.                 if (typeof(type) == "string")
  4642.                     type = {
  4643.                         style : type,
  4644.                         type : type
  4645.                     };
  4646.     
  4647.                 type.content = (type.content || "") + source.get();
  4648.                 if (!/\n$/.test(type.content))
  4649.                     source.nextWhile(isWhiteSpace);
  4650.                 type.value = type.content + source.get();
  4651.                 return type;
  4652.             },
  4653.     
  4654.             next : function() {
  4655.                 if (!source.more())
  4656.                     throw StopIteration;
  4657.     
  4658.                 var type;
  4659.                 if (source.equals("\n")) {
  4660.                     source.next();
  4661.                     return this.take("whitespace");
  4662.                 }
  4663.     
  4664.                 if (source.applies(isWhiteSpace))
  4665.                     type = "whitespace";
  4666.                 else
  4667.                     while (!type)
  4668.                         type = this.state(source, function(s) {
  4669.                                     tokenizer.state = s;
  4670.                                 });
  4671.     
  4672.                 return this.take(type);
  4673.             }
  4674.         };
  4675.         return tokenizer;
  4676.     }
  4677.     
  4678.     /*
  4679.      * String streams are the things fed to parsers (which can feed them to a
  4680.      * tokenizer if they want). They provide peek and next methods for looking at
  4681.      * the current character (next 'consumes' this character, peek does not), and a
  4682.      * get method for retrieving all the text that was consumed since the last time
  4683.      * get was called.
  4684.      * 
  4685.      * An easy mistake to make is to let a StopIteration exception finish the token
  4686.      * stream while there are still characters pending in the string stream (hitting
  4687.      * the end of the buffer while parsing a token). To make it easier to detect
  4688.      * such errors, the stringstreams throw an exception when this happens.
  4689.      */
  4690.     
  4691.     // Make a stringstream stream out of an iterator that returns strings.
  4692.     // This is applied to the result of traverseDOM (see codemirror.js),
  4693.     // and the resulting stream is fed to the parser.
  4694.     var stringStream = function(source) {
  4695.         // String that's currently being iterated over.
  4696.         var current = "";
  4697.         // Position in that string.
  4698.         var pos = 0;
  4699.         // Accumulator for strings that have been iterated over but not
  4700.         // get()-ed yet.
  4701.         var accum = "";
  4702.         
  4703.         // ZC fix: if we've passed a string, wrap it with traverseDOM-like interface
  4704.         if (typeof source == 'string') {
  4705.             var _source = source,
  4706.                 _fed = false;
  4707.             source = {
  4708.                 next: function() {
  4709.                     if (!_fed) {
  4710.                         _fed = true;
  4711.                         return _source;
  4712.                     } else {
  4713.                         throw StopIteration;
  4714.                     }
  4715.                 }
  4716.             }
  4717.         }
  4718.         
  4719.         // Make sure there are more characters ready, or throw
  4720.         // StopIteration.
  4721.         function ensureChars() {
  4722.             while (pos == current.length) {
  4723.                 accum += current;
  4724.                 current = ""; // In case source.next() throws
  4725.                 pos = 0;
  4726.                 try {
  4727.                     current = source.next();
  4728.                 } catch (e) {
  4729.                     if (e != StopIteration)
  4730.                         throw e;
  4731.                     else
  4732.                         return false;
  4733.                 }
  4734.             }
  4735.             return true;
  4736.         }
  4737.     
  4738.         return {
  4739.             // peek: -> character
  4740.             // Return the next character in the stream.
  4741.             peek : function() {
  4742.                 if (!ensureChars())
  4743.                     return null;
  4744.                 return current.charAt(pos);
  4745.             },
  4746.             // next: -> character
  4747.             // Get the next character, throw StopIteration if at end, check
  4748.             // for unused content.
  4749.             next : function() {
  4750.                 if (!ensureChars()) {
  4751.                     if (accum.length > 0)
  4752.                         throw "End of stringstream reached without emptying buffer ('" + accum + "').";
  4753.                     else
  4754.                         throw StopIteration;
  4755.                 }
  4756.                 return current.charAt(pos++);
  4757.             },
  4758.             // get(): -> string
  4759.             // Return the characters iterated over since the last call to
  4760.             // .get().
  4761.             get : function() {
  4762.                 var temp = accum;
  4763.                 accum = "";
  4764.                 if (pos > 0) {
  4765.                     temp += current.slice(0, pos);
  4766.                     current = current.slice(pos);
  4767.                     pos = 0;
  4768.                 }
  4769.                 return temp;
  4770.             },
  4771.             // Push a string back into the stream.
  4772.             push : function(str) {
  4773.                 current = current.slice(0, pos) + str + current.slice(pos);
  4774.             },
  4775.             lookAhead : function(str, consume, skipSpaces, caseInsensitive) {
  4776.                 function cased(str) {
  4777.                     return caseInsensitive ? str.toLowerCase() : str;
  4778.                 }
  4779.                 str = cased(str);
  4780.                 var found = false;
  4781.     
  4782.                 var _accum = accum, _pos = pos;
  4783.                 if (skipSpaces)
  4784.                     this.nextWhileMatches(/[\s\u00a0]/);
  4785.     
  4786.                 while (true) {
  4787.                     var end = pos + str.length, left = current.length - pos;
  4788.                     if (end <= current.length) {
  4789.                         found = str == cased(current.slice(pos, end));
  4790.                         pos = end;
  4791.                         break;
  4792.                     } else if (str.slice(0, left) == cased(current.slice(pos))) {
  4793.                         accum += current;
  4794.                         current = "";
  4795.                         try {
  4796.                             current = source.next();
  4797.                         } catch (e) {
  4798.                             if (e != StopIteration)
  4799.                                 throw e;
  4800.                             break;
  4801.                         }
  4802.                         pos = 0;
  4803.                         str = str.slice(left);
  4804.                     } else {
  4805.                         break;
  4806.                     }
  4807.                 }
  4808.     
  4809.                 if (!(found && consume)) {
  4810.                     current = accum.slice(_accum.length) + current;
  4811.                     pos = _pos;
  4812.                     accum = _accum;
  4813.                 }
  4814.     
  4815.                 return found;
  4816.             },
  4817.             // Wont't match past end of line.
  4818.             lookAheadRegex : function(regex, consume) {
  4819.                 if (regex.source.charAt(0) != "^")
  4820.                     throw new Error("Regexps passed to lookAheadRegex must start with ^");
  4821.     
  4822.                 // Fetch the rest of the line
  4823.                 while (current.indexOf("\n", pos) == -1) {
  4824.                     try {
  4825.                         current += source.next();
  4826.                     } catch (e) {
  4827.                         if (e != StopIteration)
  4828.                             throw e;
  4829.                         break;
  4830.                     }
  4831.                 }
  4832.                 var matched = current.slice(pos).match(regex);
  4833.                 if (matched && consume)
  4834.                     pos += matched[0].length;
  4835.                 return matched;
  4836.             },
  4837.     
  4838.             // Utils built on top of the above
  4839.             // more: -> boolean
  4840.             // Produce true if the stream isn't empty.
  4841.             more : function() {
  4842.                 return this.peek() !== null;
  4843.             },
  4844.             applies : function(test) {
  4845.                 var next = this.peek();
  4846.                 return (next !== null && test(next));
  4847.             },
  4848.             nextWhile : function(test) {
  4849.                 var next;
  4850.                 while ((next = this.peek()) !== null && test(next))
  4851.                     this.next();
  4852.             },
  4853.             matches : function(re) {
  4854.                 var next = this.peek();
  4855.                 return (next !== null && re.test(next));
  4856.             },
  4857.             nextWhileMatches : function(re) {
  4858.                 var next;
  4859.                 while ((next = this.peek()) !== null && re.test(next))
  4860.                     this.next();
  4861.             },
  4862.             equals : function(ch) {
  4863.                 return ch === this.peek();
  4864.             },
  4865.             endOfLine : function() {
  4866.                 var next = this.peek();
  4867.                 return next == null || next == "\n";
  4868.             }
  4869.         };
  4870.     };
  4871.  
  4872.     
  4873.     
  4874.   var Kludges = {
  4875.     autoSelfClosers: {"br": true, "img": true, "hr": true, "link": true, "input": true,
  4876.                       "meta": true, "col": true, "frame": true, "base": true, "area": true},
  4877.     doNotIndent: {"pre": true, "!cdata": true}
  4878.   };
  4879.   var NoKludges = {autoSelfClosers: {}, doNotIndent: {"!cdata": true}};
  4880.   var UseKludges = Kludges;
  4881.   var alignCDATA = false;
  4882.  
  4883.   // Simple stateful tokenizer for XML documents. Returns a
  4884.   // MochiKit-style iterator, with a state property that contains a
  4885.   // function encapsulating the current state. See tokenize.js.
  4886.   var tokenizeXML = (function() {
  4887.     function inText(source, setState) {
  4888.       var ch = source.next();
  4889.       if (ch == "<") {
  4890.         if (source.equals("!")) {
  4891.           source.next();
  4892.           if (source.equals("[")) {
  4893.             if (source.lookAhead("[CDATA[", true)) {
  4894.               setState(inBlock("xml-cdata", "]]>"));
  4895.               return null;
  4896.             }
  4897.             else {
  4898.               return "xml-text";
  4899.             }
  4900.           }
  4901.           else if (source.lookAhead("--", true)) {
  4902.             setState(inBlock("xml-comment", "-->"));
  4903.             return null;
  4904.           }
  4905.           else if (source.lookAhead("DOCTYPE", true)) {
  4906.             source.nextWhileMatches(/[\w\._\-]/);
  4907.             setState(inBlock("xml-doctype", ">"));
  4908.             return "xml-doctype";
  4909.           }
  4910.           else {
  4911.             return "xml-text";
  4912.           }
  4913.         }
  4914.         else if (source.equals("?")) {
  4915.           source.next();
  4916.           source.nextWhileMatches(/[\w\._\-]/);
  4917.           setState(inBlock("xml-processing", "?>"));
  4918.           return "xml-processing";
  4919.         }
  4920.         else {
  4921.           if (source.equals("/")) source.next();
  4922.           setState(inTag);
  4923.           return "xml-punctuation";
  4924.         }
  4925.       }
  4926.       else if (ch == "&") {
  4927.         while (!source.endOfLine()) {
  4928.           if (source.next() == ";")
  4929.             break;
  4930.         }
  4931.         return "xml-entity";
  4932.       }
  4933.       else {
  4934.         source.nextWhileMatches(/[^&<\n]/);
  4935.         return "xml-text";
  4936.       }
  4937.     }
  4938.  
  4939.     function inTag(source, setState) {
  4940.       var ch = source.next();
  4941.       if (ch == ">") {
  4942.         setState(inText);
  4943.         return "xml-punctuation";
  4944.       }
  4945.       else if (/[?\/]/.test(ch) && source.equals(">")) {
  4946.         source.next();
  4947.         setState(inText);
  4948.         return "xml-punctuation";
  4949.       }
  4950.       else if (ch == "=") {
  4951.         return "xml-punctuation";
  4952.       }
  4953.       else if (/[\'\"]/.test(ch)) {
  4954.         setState(inAttribute(ch));
  4955.         return null;
  4956.       }
  4957.       else {
  4958.         source.nextWhileMatches(/[^\s\u00a0=<>\"\'\/?]/);
  4959.         return "xml-name";
  4960.       }
  4961.     }
  4962.  
  4963.     function inAttribute(quote) {
  4964.       return function(source, setState) {
  4965.         while (!source.endOfLine()) {
  4966.           if (source.next() == quote) {
  4967.             setState(inTag);
  4968.             break;
  4969.           }
  4970.         }
  4971.         return "xml-attribute";
  4972.       };
  4973.     }
  4974.  
  4975.     function inBlock(style, terminator) {
  4976.       return function(source, setState) {
  4977.         while (!source.endOfLine()) {
  4978.           if (source.lookAhead(terminator, true)) {
  4979.             setState(inText);
  4980.             break;
  4981.           }
  4982.           source.next();
  4983.         }
  4984.         return style;
  4985.       };
  4986.     }
  4987.  
  4988.     return function(source, startState) {
  4989.       return tokenizer(source, startState || inText);
  4990.     };
  4991.   })();
  4992.  
  4993.   // The parser. The structure of this function largely follows that of
  4994.   // parseJavaScript in parsejavascript.js (there is actually a bit more
  4995.   // shared code than I'd like), but it is quite a bit simpler.
  4996.   function parseXML(source) {
  4997.     var tokens = tokenizeXML(source), token;
  4998.     var cc = [base];
  4999.     var tokenNr = 0, indented = 0;
  5000.     var currentTag = null, context = null;
  5001.     var consume;
  5002.     
  5003.     function push(fs) {
  5004.       for (var i = fs.length - 1; i >= 0; i--)
  5005.         cc.push(fs[i]);
  5006.     }
  5007.     function cont() {
  5008.       push(arguments);
  5009.       consume = true;
  5010.     }
  5011.     function pass() {
  5012.       push(arguments);
  5013.       consume = false;
  5014.     }
  5015.  
  5016.     function markErr() {
  5017.       token.style += " xml-error";
  5018.     }
  5019.     function expect(text) {
  5020.       return function(style, content) {
  5021.         if (content == text) cont();
  5022.         else {markErr(); cont(arguments.callee);}
  5023.       };
  5024.     }
  5025.  
  5026.     function pushContext(tagname, startOfLine) {
  5027.       var noIndent = UseKludges.doNotIndent.hasOwnProperty(tagname) || (context && context.noIndent);
  5028.       context = {prev: context, name: tagname, indent: indented, startOfLine: startOfLine, noIndent: noIndent};
  5029.     }
  5030.     function popContext() {
  5031.       context = context.prev;
  5032.     }
  5033.     function computeIndentation(baseContext) {
  5034.       return function(nextChars, current) {
  5035.         var context = baseContext;
  5036.         if (context && context.noIndent)
  5037.           return current;
  5038.         if (alignCDATA && /<!\[CDATA\[/.test(nextChars))
  5039.           return 0;
  5040.         if (context && /^<\//.test(nextChars))
  5041.           context = context.prev;
  5042.         while (context && !context.startOfLine)
  5043.           context = context.prev;
  5044.         if (context)
  5045.           return context.indent + indentUnit;
  5046.         else
  5047.           return 0;
  5048.       };
  5049.     }
  5050.  
  5051.     function base() {
  5052.       return pass(element, base);
  5053.     }
  5054.     var harmlessTokens = {"xml-text": true, "xml-entity": true, "xml-comment": true, "xml-processing": true, "xml-doctype": true};
  5055.     function element(style, content) {
  5056.       if (content == "<") cont(tagname, attributes, endtag(tokenNr == 1));
  5057.       else if (content == "</") cont(closetagname, expect(">"));
  5058.       else if (style == "xml-cdata") {
  5059.         if (!context || context.name != "!cdata") pushContext("!cdata");
  5060.         if (/\]\]>$/.test(content)) popContext();
  5061.         cont();
  5062.       }
  5063.       else if (harmlessTokens.hasOwnProperty(style)) cont();
  5064.       else {markErr(); cont();}
  5065.     }
  5066.     function tagname(style, content) {
  5067.       if (style == "xml-name") {
  5068.         currentTag = content.toLowerCase();
  5069.         token.style = "xml-tagname";
  5070.         cont();
  5071.       }
  5072.       else {
  5073.         currentTag = null;
  5074.         pass();
  5075.       }
  5076.     }
  5077.     function closetagname(style, content) {
  5078.       if (style == "xml-name") {
  5079.         token.style = "xml-tagname";
  5080.         if (context && content.toLowerCase() == context.name) popContext();
  5081.         else markErr();
  5082.       }
  5083.       cont();
  5084.     }
  5085.     function endtag(startOfLine) {
  5086.       return function(style, content) {
  5087.         if (content == "/>" || (content == ">" && UseKludges.autoSelfClosers.hasOwnProperty(currentTag))) cont();
  5088.         else if (content == ">") {pushContext(currentTag, startOfLine); cont();}
  5089.         else {markErr(); cont(arguments.callee);}
  5090.       };
  5091.     }
  5092.     function attributes(style) {
  5093.       if (style == "xml-name") {token.style = "xml-attname"; cont(attribute, attributes);}
  5094.       else pass();
  5095.     }
  5096.     function attribute(style, content) {
  5097.       if (content == "=") cont(value);
  5098.       else if (content == ">" || content == "/>") pass(endtag);
  5099.       else pass();
  5100.     }
  5101.     function value(style) {
  5102.       if (style == "xml-attribute") cont(value);
  5103.       else pass();
  5104.     }
  5105.  
  5106.     return {
  5107.       indentation: function() {return indented;},
  5108.  
  5109.       next: function(){
  5110.         token = tokens.next();
  5111.         if (token.style == "whitespace" && tokenNr == 0)
  5112.           indented = token.value.length;
  5113.         else
  5114.           tokenNr++;
  5115.         if (token.content == "\n") {
  5116.           indented = tokenNr = 0;
  5117.           token.indentation = computeIndentation(context);
  5118.         }
  5119.  
  5120.         if (token.style == "whitespace" || token.type == "xml-comment")
  5121.           return token;
  5122.  
  5123.         while(true){
  5124.           consume = false;
  5125.           cc.pop()(token.style, token.content);
  5126.           if (consume) return token;
  5127.         }
  5128.       },
  5129.  
  5130.       copy: function(){
  5131.         var _cc = cc.concat([]), _tokenState = tokens.state, _context = context;
  5132.         var parser = this;
  5133.         
  5134.         return function(input){
  5135.           cc = _cc.concat([]);
  5136.           tokenNr = indented = 0;
  5137.           context = _context;
  5138.           tokens = tokenizeXML(input, _tokenState);
  5139.           return parser;
  5140.         };
  5141.       }
  5142.     };
  5143.   }
  5144.  
  5145.   return {
  5146.     make: function(stream) {
  5147.         if (typeof stream == 'string')
  5148.             stream = stringStream(stream);
  5149.             
  5150.         return parseXML(stream);
  5151.     }
  5152.   };
  5153. })();
  5154. /**
  5155.  * @author Stoyan Stefanov
  5156.  * @link https://github.com/stoyan/etc/tree/master/cssex
  5157.  */
  5158. var CSSEX = (function () {
  5159.      
  5160.     var walker, tokens = [], isOp, isNameChar, isDigit;
  5161.     
  5162.     // walks around the source
  5163.     walker = {
  5164.         lines: null,
  5165.         total_lines: 0,
  5166.         linenum: -1,
  5167.         line: '',
  5168.         ch: '',
  5169.         chnum: -1,
  5170.         init: function (source) {
  5171.             var me = walker;
  5172.         
  5173.             // source, yumm
  5174.             me.lines = source
  5175.                 .replace(/\r\n/g, '\n')
  5176.                 .replace(/\r/g, '\n')
  5177.                 .split('\n');
  5178.             me.total_lines = me.lines.length;
  5179.         
  5180.             // reset
  5181.             me.chnum = -1;
  5182.             me.linenum = -1;
  5183.             me.ch = '';
  5184.             me.line = '';
  5185.         
  5186.             // advance
  5187.             me.nextLine();
  5188.             me.nextChar();
  5189.         },
  5190.         nextLine: function () {
  5191.             var me = this;
  5192.             me.linenum += 1;
  5193.             if (me.total_lines <= me.linenum) {
  5194.                 me.line = false;
  5195.             } else {
  5196.                 me.line = me.lines[me.linenum];
  5197.             }
  5198.             if (me.chnum !== -1) {
  5199.                 me.chnum = 0;
  5200.             }
  5201.             return me.line;
  5202.         }, 
  5203.         nextChar: function () {
  5204.             var me = this;
  5205.             me.chnum += 1;
  5206.             while (me.line.charAt(me.chnum) === '') {
  5207.                 if (this.nextLine() === false) {
  5208.                     me.ch = false;
  5209.                     return false; // end of source
  5210.                 }
  5211.                 me.chnum = -1;
  5212.                 me.ch = '\n';
  5213.                 return '\n';
  5214.             }
  5215.             me.ch = me.line.charAt(me.chnum);
  5216.             return me.ch;
  5217.         },
  5218.         peek: function() {
  5219.             return this.line.charAt(this.chnum + 1);
  5220.         }
  5221.     };
  5222.  
  5223.     // utility helpers
  5224.     isNameChar = function (c) {
  5225.         return (c === '_' || c === '-' || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'));
  5226.     };
  5227.  
  5228.     isDigit = function (ch) {
  5229.         return (ch !== false && ch >= '0' && ch <= '9');
  5230.     };  
  5231.  
  5232.     isOp = (function () {
  5233.         var opsa = "{}[]()+*=.,;:>~|\\%$#@^!".split(''),
  5234.             opsmatcha = "*^|$~".split(''),
  5235.             ops = {},
  5236.             opsmatch = {},
  5237.             i = 0;
  5238.         for (; i < opsa.length; i += 1) {
  5239.             ops[opsa[i]] = true;
  5240.         }
  5241.         for (i = 0; i < opsmatcha.length; i += 1) {
  5242.             opsmatch[opsmatcha[i]] = true;
  5243.         }
  5244.         return function (ch, matchattr) {
  5245.             if (matchattr) {
  5246.                 return !!opsmatch[ch];
  5247.             }
  5248.             return !!ops[ch];
  5249.         };
  5250.     }());
  5251.     
  5252.     // shorthands
  5253.     function isset(v) {
  5254.         return typeof v !== 'undefined';
  5255.     }
  5256.     function getConf() {
  5257.         return {
  5258.             'char': walker.chnum,
  5259.             line: walker.linenum
  5260.         };
  5261.     }
  5262.  
  5263.  
  5264.     // creates token objects and pushes them to a list
  5265.     function tokener(value, type, conf) {
  5266.         var w = walker, c = conf || {};
  5267.         tokens.push({
  5268.             charstart: isset(c['char']) ? c['char'] : w.chnum,
  5269.             charend:   isset(c.charend) ? c.charend : w.chnum,
  5270.             linestart: isset(c.line)    ? c.line    : w.linenum,
  5271.             lineend:   isset(c.lineend) ? c.lineend : w.linenum,
  5272.             value:     value,
  5273.             type:      type || value
  5274.         });
  5275.     }
  5276.     
  5277.     // oops
  5278.     function error(m, config) { 
  5279.         var w = walker,
  5280.             conf = config || {},
  5281.             c = isset(conf['char']) ? conf['char'] : w.chnum,
  5282.             l = isset(conf.line) ? conf.line : w.linenum;
  5283.         return {
  5284.             name: "ParseError",
  5285.             message: m + " at line " + (l + 1) + ' char ' + (c + 1),
  5286.             walker: w,
  5287.             tokens: tokens
  5288.         };
  5289.     }
  5290.  
  5291.  
  5292.     // token handlers follow for:
  5293.     // white space, comment, string, identifier, number, operator
  5294.     function white() {
  5295.     
  5296.         var c = walker.ch,
  5297.             token = '',
  5298.             conf = getConf();
  5299.     
  5300.         while (c === " " || c === "\t") {
  5301.             token += c;
  5302.             c = walker.nextChar();
  5303.         }
  5304.     
  5305.         tokener(token, 'white', conf);
  5306.     
  5307.     }
  5308.  
  5309.     function comment() {
  5310.     
  5311.         var w = walker,
  5312.             c = w.ch,
  5313.             token = c,
  5314.             cnext,
  5315.             conf = getConf();    
  5316.      
  5317.         cnext = w.nextChar();
  5318.         
  5319.         if (cnext !== '*') {
  5320.             // oops, not a comment, just a /
  5321.             conf.charend = conf['char'];
  5322.             conf.lineend = conf.line;
  5323.             return tokener(token, token, conf);
  5324.         }
  5325.     
  5326.         while (!(c === "*" && cnext === "/")) {
  5327.             token += cnext;
  5328.             c = cnext;
  5329.             cnext = w.nextChar();        
  5330.         }
  5331.         token += cnext;
  5332.         w.nextChar();
  5333.         tokener(token, 'comment', conf);
  5334.     }
  5335.  
  5336.     function str() {
  5337.         var w = walker,
  5338.             c = w.ch,
  5339.             q = c,
  5340.             token = c,
  5341.             cnext,
  5342.             conf = getConf();
  5343.     
  5344.         c = w.nextChar();
  5345.     
  5346.         while (c !== q) {
  5347.             
  5348.             if (c === '\n') {
  5349.                 cnext = w.nextChar();
  5350.                 if (cnext === "\\") {
  5351.                     token += c + cnext;
  5352.                 } else {
  5353.                     // end of line with no \ escape = bad
  5354.                     throw error("Unterminated string", conf);
  5355.                 }
  5356.             } else {
  5357.                 if (c === "\\") {
  5358.                     token += c + w.nextChar();
  5359.                 } else {
  5360.                     token += c;
  5361.                 }
  5362.             }
  5363.         
  5364.             c = w.nextChar();
  5365.         
  5366.         }
  5367.         token += c;
  5368.         w.nextChar();
  5369.         tokener(token, 'string', conf);
  5370.     }
  5371.     
  5372.     function brace() {
  5373.         var w = walker,
  5374.             c = w.ch,
  5375.             depth = 0,
  5376.             token = c,
  5377.             conf = getConf();
  5378.     
  5379.         c = w.nextChar();
  5380.     
  5381.         while (c !== ')' && !depth) {
  5382.             if (c === '(') {
  5383.                 depth++;
  5384.             } else if (c === ')') {
  5385.                 depth--;
  5386.             } else if (c === false) {
  5387.                 throw error("Unterminated brace", conf);
  5388.             }
  5389.             
  5390.             token += c;
  5391.             c = w.nextChar();
  5392.         }
  5393.         
  5394.         token += c;
  5395.         w.nextChar();
  5396.         tokener(token, 'brace', conf);
  5397.     }
  5398.  
  5399.     function identifier(pre) {
  5400.         var w = walker,
  5401.             c = w.ch,
  5402.             conf = getConf(),
  5403.             token = (pre) ? pre + c : c;
  5404.             
  5405.         c = w.nextChar();
  5406.     
  5407.         if (pre) { // adjust token position
  5408.             conf['char'] -= pre.length;
  5409.         }
  5410.         
  5411.         while (isNameChar(c) || isDigit(c)) {
  5412.             token += c;
  5413.             c = w.nextChar();
  5414.         }
  5415.     
  5416.         tokener(token, 'identifier', conf);    
  5417.     }
  5418.  
  5419.     function num() {
  5420.         var w = walker,
  5421.             c = w.ch,
  5422.             conf = getConf(),
  5423.             token = c,
  5424.             point = token === '.',
  5425.             nondigit;
  5426.         
  5427.         c = w.nextChar();
  5428.         nondigit = !isDigit(c);
  5429.     
  5430.         // .2px or .classname?
  5431.         if (point && nondigit) {
  5432.             // meh, NaN, could be a class name, so it's an operator for now
  5433.             conf.charend = conf['char'];
  5434.             conf.lineend = conf.line;
  5435.             return tokener(token, '.', conf);    
  5436.         }
  5437.         
  5438.         // -2px or -moz-something
  5439.         if (token === '-' && nondigit) {
  5440.             return identifier('-');
  5441.         }
  5442.     
  5443.         while (c !== false && (isDigit(c) || (!point && c === '.'))) { // not end of source && digit or first instance of .
  5444.             if (c === '.') {
  5445.                 point = true;
  5446.             }
  5447.             token += c;
  5448.             c = w.nextChar();
  5449.         }
  5450.  
  5451.         tokener(token, 'number', conf);    
  5452.     
  5453.     }
  5454.  
  5455.     function op() {
  5456.         var w = walker,
  5457.             c = w.ch,
  5458.             conf = getConf(),
  5459.             token = c,
  5460.             next = w.nextChar();
  5461.             
  5462.         if (next === "=" && isOp(token, true)) {
  5463.             token += next;
  5464.             tokener(token, 'match', conf);
  5465.             w.nextChar();
  5466.             return;
  5467.         } 
  5468.         
  5469.         conf.charend = conf['char'] + 1;
  5470.         conf.lineend = conf.line;    
  5471.         tokener(token, token, conf);
  5472.     }
  5473.  
  5474.  
  5475.     // call the appropriate handler based on the first character in a token suspect
  5476.     function tokenize() {
  5477.  
  5478.         var ch = walker.ch;
  5479.     
  5480.         if (ch === " " || ch === "\t") {
  5481.             return white();
  5482.         }
  5483.  
  5484.         if (ch === '/') {
  5485.             return comment();
  5486.         } 
  5487.  
  5488.         if (ch === '"' || ch === "'") {
  5489.             return str();
  5490.         }
  5491.         
  5492.         if (ch === '(') {
  5493.             return brace();
  5494.         }
  5495.     
  5496.         if (ch === '-' || ch === '.' || isDigit(ch)) { // tricky - char: minus (-1px) or dash (-moz-stuff)
  5497.             return num();
  5498.         }
  5499.     
  5500.         if (isNameChar(ch)) {
  5501.             return identifier();
  5502.         }
  5503.  
  5504.         if (isOp(ch)) {
  5505.             return op();
  5506.         }
  5507.         
  5508.         if (ch === "\n") {
  5509.             tokener("line");
  5510.             walker.nextChar();
  5511.             return;
  5512.         }
  5513.         
  5514.         throw error("Unrecognized character");
  5515.     }
  5516.  
  5517.  
  5518.     return {
  5519.         lex: function (source) {
  5520.             walker.init(source);
  5521.             tokens = [];
  5522.             while (walker.ch !== false) {
  5523.                 tokenize();            
  5524.             }
  5525.             return tokens;
  5526.         },
  5527.         toSource: function (toks) {
  5528.             var i = 0, max = toks.length, t, src = '';
  5529.             for (; i < max; i += 1) {
  5530.                 t = toks[i];
  5531.                 if (t.type === 'line') {
  5532.                     src += '\n';
  5533.                 } else {
  5534.                     src += t.value;
  5535.                 }
  5536.             }
  5537.             return src;
  5538.         }
  5539.     };
  5540.  
  5541.  
  5542.  
  5543. }());/**
  5544.  * Some utility functions for CSS parser:
  5545.  * -- optimizes CSS lexer token, produced by Stoyan Stefanov's CSSEX parser,
  5546.  *    for Zen Coding needs
  5547.  * -- extracts full CSS rule (selector + style rules) from content
  5548.  *  
  5549.  * @author Sergey Chikuyonok (serge.che@gmail.com)
  5550.  * @link http://chikuyonok.ru
  5551.  * 
  5552.  * @include "sex.js"
  5553.  */var ParserUtils = (function() {
  5554.     var css_stop_chars = '{}/\\<>';
  5555.     
  5556.     function isStopChar(token) {
  5557.         var stop_chars = '{};:';
  5558.         return stop_chars.indexOf(token.type) != -1;
  5559.     }
  5560.     
  5561.     /**
  5562.      * Calculates newline width at specified position in content
  5563.      * @param {String} content
  5564.      * @param {Number} pos
  5565.      * @return {Number}
  5566.      */
  5567.     function calculateNlLength(content, pos) {
  5568.         return content.charAt(pos) == '\r' && content.charAt(pos + 1) == '\n' ? 2 : 1;
  5569.     }
  5570.     
  5571.     /**
  5572.      * Post-process optmized tokens: collapse tokens for complex values
  5573.      * @param {Array} optimized Optimized tokens
  5574.      * @param {Array} original Original preprocessed tokens 
  5575.      */
  5576.     function postProcessOptimized(optimized, original) {
  5577.         var token, child;
  5578.         for (var i = 0, il = optimized.length; i < il; i++) {
  5579.             token = optimized[i];
  5580.             if (token.type == 'value') {
  5581.                 token.children = [];
  5582.                 child = null;
  5583.                 
  5584.                 var subtoken_start = token.ref_start_ix;
  5585.                     
  5586.                 while (subtoken_start <= token.ref_end_ix) {
  5587.                     var subtoken = original[subtoken_start];
  5588.                     if (subtoken.type != 'white') {
  5589.                         if (!child)
  5590.                             child = [subtoken.start, subtoken.end];
  5591.                         else
  5592.                             child[1] = subtoken.end;
  5593.                     } else if (child) {
  5594.                         token.children.push(child);
  5595.                         child = null;
  5596.                     }
  5597.                     
  5598.                     subtoken_start++;    
  5599.                 }
  5600.                 
  5601.                 if (child) // push last token
  5602.                     token.children.push(child);
  5603.             }
  5604.         }
  5605.         
  5606.         return optimized;
  5607.     }
  5608.     
  5609.     function makeToken(type, value, pos, ix) {
  5610.         value = value || '';
  5611.         return {
  5612.             type: type || '',
  5613.             content: value,
  5614.             start: pos,
  5615.             end: pos + value.length,
  5616.             /** Reference token index that starts current token */
  5617.             ref_start_ix: ix,
  5618.             /** Reference token index that ends current token */
  5619.             ref_end_ix: ix
  5620.         }
  5621.     }
  5622.     
  5623.     return {
  5624.         /**
  5625.          * Parses CSS and optimizes parsed chunks
  5626.          * @see ParserUtils#optimizeCSS
  5627.          * @param {String} source CSS source code fragment
  5628.          * @param {Number} offset Offset of CSS fragment inside whole document
  5629.          * @return {Array}
  5630.          */
  5631.         parseCSS: function(source, offset) {
  5632.             return this.optimizeCSS(CSSEX.lex(source), offset || 0, source);
  5633.         },
  5634.         
  5635.         /**
  5636.          * Parses HTML and optimizes parsed chunks
  5637.          * @param {String} source HTML source code fragment
  5638.          * @param {Number} offset Offset of HTML fragment inside whole document
  5639.          * @return {Array}
  5640.          */
  5641.         parseHTML: function(tag, offset) {
  5642.             var tokens = XMLParser.make(tag),
  5643.                 result = [],
  5644.                 t, i = 0;
  5645.                 
  5646.             try {
  5647.                 while (t = tokens.next()) {
  5648. //                    result.push(tagDef(offset + i, t));
  5649.                     result.push(makeToken(t.style, t.content, offset + i, 0));
  5650.                     i += t.value.length;
  5651.                 }
  5652.             } catch (e) {
  5653.                 if (e != 'StopIteration') throw e;
  5654.             }
  5655.             
  5656.             return result;
  5657.         },
  5658.         
  5659.         /**
  5660.          * Optimizes parsed CSS tokens: combines selector chunks, complex values
  5661.          * into a single chunk
  5662.          * @param {Array} tokens Tokens produced by <code>CSSEX.lex()</code>
  5663.          * @param {Number} offset CSS rule offset in source code (character index)
  5664.          * @param {String} content Original CSS source code
  5665.          * @return {Array} Optimized tokens  
  5666.          */
  5667.         optimizeCSS: function(tokens, offset, content) {
  5668.             offset = offset || 0;
  5669.             var result = [], token, i, il, _o = 0,
  5670.                 in_rules = false,
  5671.                 in_value = false,
  5672.                 delta = 0,
  5673.                 acc_type,
  5674.                 acc_tokens = {
  5675.                     /** @type {makeToken} */
  5676.                     selector: null,
  5677.                     /** @type {makeToken} */
  5678.                     value: null
  5679.                 },
  5680.                 nl_size,
  5681.                 orig_tokens = [];
  5682.                 
  5683.             function addToken(token, type) {
  5684.                 if (type && type in acc_tokens) {
  5685.                     if (!acc_tokens[type]) {
  5686.                         acc_tokens[type] = makeToken(type, token.value, offset + delta + token.charstart, i);
  5687.                         result.push(acc_tokens[type]);
  5688.                     } else {
  5689.                         acc_tokens[type].content += token.value;
  5690.                         acc_tokens[type].end += token.value.length;
  5691.                         acc_tokens[type].ref_end_ix = i;
  5692.                     }
  5693.                 } else {
  5694.                     result.push(makeToken(token.type, token.value, offset + delta + token.charstart, i));
  5695.                 }
  5696.             }
  5697.             
  5698.             for (i = 0, il = tokens.length; i < il; i++) {
  5699.                 token = tokens[i];
  5700.                 acc_type = null;
  5701.                 
  5702.                 if (token.type == 'line') {
  5703.                     delta += _o;
  5704.                     nl_size = content ? calculateNlLength(content, delta) : 1;
  5705.                     
  5706.                     var tok_value = nl_size == 1 ? '\n' : '\r\n';
  5707.                     orig_tokens.push(makeToken(token.type, tok_value, offset + delta));
  5708.                     
  5709.                     result.push(makeToken(token.type, tok_value, offset + delta, i));
  5710.                     delta += nl_size;
  5711.                     _o = 0;
  5712.                     
  5713.                     continue;
  5714.                 }
  5715.                 
  5716.                 orig_tokens.push(makeToken(token.type, token.value, offset + delta + token.charstart));
  5717.                 
  5718. //                _o = token.charend;
  5719.                 // use charstart and length because of incorrect charend 
  5720.                 // computation for whitespace
  5721.                 _o = token.charstart + token.value.length;
  5722.                 
  5723.                 if (token.type != 'white') {
  5724.                     if (token.type == '{') {
  5725.                         in_rules = true;
  5726.                         acc_tokens.selector = null;
  5727.                     } else if (in_rules) {
  5728.                         if (token.type == ':') {
  5729.                             in_value = true;
  5730.                         } else if (token.type == ';') {
  5731.                             in_value = false;
  5732.                             acc_tokens.value = null;
  5733.                         }  else if (token.type == '}') {
  5734.                             in_value = in_rules = false;
  5735.                             acc_tokens.value = null;
  5736.                         } else if (in_value || acc_tokens.value) {
  5737.                             acc_type = 'value';
  5738.                         }
  5739.                     } else if (acc_tokens.selector || (!in_rules && !isStopChar(token))) {
  5740.                         // start selector token
  5741.                         acc_type = 'selector';
  5742.                     }
  5743.                     
  5744.                     addToken(token, acc_type);
  5745.                 } else {
  5746.                     // whitespace token, decide where it should be
  5747.                     if (i < il - 1 && isStopChar(tokens[i + 1])) continue;
  5748.                     
  5749.                     if (acc_tokens.selector || acc_tokens.value)
  5750.                         addToken(token, acc_tokens.selector ? 'selector' : 'value');
  5751.                 }
  5752.             }
  5753.             
  5754.             result.__original = orig_tokens;
  5755.             return postProcessOptimized(result, orig_tokens);
  5756.         },
  5757.         
  5758.         /**
  5759.          * Extracts single CSS selector definition from source code
  5760.          * @param {String} content CSS source code
  5761.          * @param {Number} pos Character position where to start source code extraction
  5762.          */
  5763.         extractCSSRule: function(content, pos, is_backward) {
  5764.             var result = '', 
  5765.                 c_len = content.length,
  5766.                 offset = pos, 
  5767.                 brace_pos = -1, ch;
  5768.             
  5769.             // search left until we find rule edge
  5770.             while (offset >= 0) {
  5771.                 ch = content.charAt(offset);
  5772.                 if (ch == '{') {
  5773.                     brace_pos = offset;
  5774.                     break;
  5775.                 }
  5776.                 else if (ch == '}' && !is_backward) {
  5777.                     offset++;
  5778.                     break;
  5779.                 }
  5780.                 
  5781.                 offset--;
  5782.             }
  5783.             
  5784.             // search right for full rule set
  5785.             while (offset < c_len) {
  5786.                 ch = content.charAt(offset);
  5787.                 if (ch == '{')
  5788.                     brace_pos = offset;
  5789.                 else if (ch == '}') {
  5790.                     if (brace_pos != -1)
  5791.                         result = content.substring(brace_pos, offset + 1);
  5792.                     break;
  5793.                 }
  5794.                 
  5795.                 offset++;
  5796.             }
  5797.             
  5798.             if (result) {
  5799.                 // find CSS selector
  5800.                 offset = brace_pos - 1;
  5801.                 var selector = '';
  5802.                 while (offset >= 0) {
  5803.                     ch = content.charAt(offset);
  5804.                     if (css_stop_chars.indexOf(ch) != -1) break;
  5805.                     offset--;
  5806.                 }
  5807.                 
  5808.                 // also trim whitespace
  5809.                 selector = content.substring(offset + 1, brace_pos).replace(/^[\s\n\r]+/m, '');
  5810.                 return [brace_pos - selector.length, brace_pos + result.length];
  5811.             }
  5812.             
  5813.             return null;
  5814.         },
  5815.         
  5816.         token: makeToken
  5817.     };
  5818. })();
  5819. /**
  5820.  * @author Sergey Chikuyonok (serge.che@gmail.com)
  5821.  * @link http://chikuyonok.ru
  5822.  * 
  5823.  * @include "../zen_editor.js"
  5824.  * @include "parserutils.js"
  5825.  * @include "../zen_coding.js"
  5826.  * @include "../zen_actions.js"
  5827.  */
  5828.  
  5829. /**
  5830.  * Reflect CSS value: takes rule's value under caret and pastes it for the same 
  5831.  * rules with vendor prefixes
  5832.  * @param {zen_editor} editor
  5833.  */
  5834. function reflectCSSValue(editor) {
  5835.     if (editor.getSyntax() != 'css') return false;
  5836.     
  5837.     return compoundUpdate(editor, doCSSReflection(editor));
  5838. }
  5839.  
  5840. /**
  5841.  * Update image size: reads image from image/CSS rule under caret
  5842.  * and updates dimensions inside tag/rule
  5843.  * @param {zen_editor} editor
  5844.  */
  5845. function updateImageSize(editor) {
  5846.     var result;
  5847.     if (String(editor.getSyntax()) == 'css') {
  5848.         result = updateImageSizeCSS(editor);
  5849.     } else {
  5850.         result = updateImageSizeHTML(editor);
  5851.     }
  5852.     
  5853.     return compoundUpdate(editor, result);
  5854. }
  5855.  
  5856. function compoundUpdate(editor, data) {
  5857.     if (data) {
  5858.         var sel = editor.getSelectionRange();
  5859.         editor.replaceContent(data.data, data.start, data.end, true);
  5860.         editor.createSelection(data.caret, data.caret + sel.end - sel.start);
  5861.         return true;
  5862.     }
  5863.     
  5864.     return false;
  5865. }
  5866.  
  5867. /**
  5868.  * Updates image size of <img src=""> tag
  5869.  * @param {zen_editor} editor
  5870.  */
  5871. function updateImageSizeHTML(editor) {
  5872.     var offset = editor.getCaretPos();
  5873.         
  5874.     var image = findImage(editor);
  5875.     if (image) {
  5876.         var re = /\bsrc=(["'])(.+?)\1/i, m, src;
  5877.         if (m = re.exec(image.tag))
  5878.             src = m[2];
  5879.         
  5880.         if (src) {
  5881.             var size = getImageSizeForSource(editor, src);
  5882.             if (size) {
  5883.                 var new_tag = replaceOrAppend(image.tag, 'width', size.width);
  5884.                 new_tag = replaceOrAppend(new_tag, 'height', size.height);
  5885.                 
  5886.                 return {
  5887.                     'data': new_tag,
  5888.                     'start': image.start,
  5889.                     'end': image.end,
  5890.                     'caret': offset
  5891.                 };
  5892.             }
  5893.         }
  5894.     }
  5895.     
  5896.     return null;
  5897. }
  5898.  
  5899. /**
  5900.  * Search for insertion point for new CSS properties
  5901.  * @param {ParserUtils.token[]} tokens
  5902.  * @param {Number} start_ix Token index where to start searching
  5903.  */
  5904. function findCSSInsertionPoint(tokens, start_ix) {
  5905.     var ins_point, 
  5906.         ins_ix = -1, 
  5907.         need_col = false;
  5908.         
  5909.     for (var i = start_ix, il = tokens.length; i < il; i++) {
  5910.         var t = tokens[i];
  5911.         if (t.type == 'value') {
  5912.             ins_point = t;
  5913.             ins_ix = i;
  5914.             // look ahead fo rule termination
  5915.             if (tokens[i + 1] && tokens[i + 1].type == ';') {
  5916.                 ins_point = tokens[i + 1];
  5917.                 ins_ix += 1;
  5918.             } else {
  5919.                 need_col = true;
  5920.             }
  5921.             break;
  5922.         }
  5923.     }
  5924.     
  5925.     return {
  5926.         token: ins_point,
  5927.         ix: ins_ix,
  5928.         need_col: need_col
  5929.     };
  5930. }
  5931.  
  5932. /**
  5933.  * Updates image size of CSS rule
  5934.  * @param {zen_editor} editor
  5935.  */
  5936. function updateImageSizeCSS(editor) {
  5937.     var caret_pos = editor.getCaretPos(),
  5938.         content = String(editor.getContent()),
  5939.         rule = ParserUtils.extractCSSRule(content, caret_pos, true);
  5940.         
  5941.     
  5942.     if (rule) {
  5943.         var css = ParserUtils.parseCSS(content.substring(rule[0], rule[1]), rule[0]),
  5944.             cur_token = findTokenFromPosition(css, caret_pos, 'identifier'),
  5945.             value = findValueToken(css, cur_token + 1),
  5946.             m;
  5947.             
  5948.         if (!value) return false;
  5949.         
  5950.         // find inserion point
  5951.         var ins_point = findCSSInsertionPoint(css, cur_token);
  5952.             
  5953.         if (m = /url\((["']?)(.+?)\1\)/i.exec(value.content)) {
  5954.             var size = getImageSizeForSource(editor, m[2]);
  5955.             if (size) {
  5956.                 var wh = {width: null, height: null},
  5957.                     updates = [],
  5958.                     styler = learnCSSStyle(css, cur_token);
  5959.                     
  5960.                 for (var i = 0, il = css.length; i < il; i++) {
  5961.                     if (css[i].type == 'identifier' && css[i].content in wh)
  5962.                         wh[css[i].content] = i;
  5963.                 }
  5964.                 
  5965.                 function update(name, val) {
  5966.                     var v;
  5967.                     if (wh[name] !== null && (v = findValueToken(css, wh[name] + 1))) {
  5968.                         updates.push([v.start, v.end, val + 'px']);
  5969.                     } else {
  5970.                         updates.push([ins_point.token.end, ins_point.token.end, styler(name, val + 'px')]);
  5971.                     }
  5972.                 }
  5973.                 
  5974.                 update('width', size.width);
  5975.                 update('height', size.height);
  5976.                 
  5977.                 if (updates.length) {
  5978.                     updates.sort(function(a, b){return a[0] - b[0]});
  5979.                     
  5980.                     // some editors do not provide easy way to replace multiple code 
  5981.                     // fragments so we have to squash all replace operations into one
  5982.                     var data = content.substring(updates[0][0], updates[updates.length - 1][1]),
  5983.                         offset = updates[0][0];
  5984.                         
  5985.                     for (var i = updates.length - 1; i >= 0; i--) {
  5986.                         var u = updates[i];
  5987.                         data = replaceSubstring(data, u[0] - offset, u[1] - offset, u[2]);
  5988.                             
  5989.                         // also calculate new caret position
  5990.                         if (u[0] < caret_pos)
  5991.                             caret_pos += u[2].length - u[1] + u[0];
  5992.                     }
  5993.                     
  5994.                     if (ins_point.need_col)
  5995.                         data = replaceSubstring(data, ins_point.token.end - offset, ins_point.token.end - offset, ';');
  5996.                     
  5997.                     return {
  5998.                         'data': data,
  5999.                         'start': offset,
  6000.                         'end': updates[updates.length - 1][1],
  6001.                         'caret': caret_pos
  6002.                     };
  6003.                     
  6004.                 }
  6005.             }
  6006.         }
  6007.     }
  6008.         
  6009.     return false;
  6010. }
  6011.  
  6012. /**
  6013.  * Learns formatting style from parsed tokens
  6014.  * @param {ParserUtils.token[]} tokens List of tokens
  6015.  * @param {Number} pos Identifier token position, from which style should be learned
  6016.  * @returns {Function} Function with <code>(name, value)</code> arguments that will create
  6017.  * CSS rule based on learned formatting
  6018.  */
  6019. function learnCSSStyle(tokens, pos) {
  6020.     var prefix = '', glue = '', i, il;
  6021.     
  6022.     // use original tokens instead of optimized ones
  6023.     pos = tokens[pos].ref_start_ix;
  6024.     tokens = tokens.__original;
  6025.     
  6026.     // learn prefix
  6027.     for (i = pos - 1; i >= 0; i--) {
  6028.         if (tokens[i].type == 'white') {
  6029.             prefix = tokens[i].content + prefix;
  6030.         } else if (tokens[i].type == 'line') {
  6031.             prefix = tokens[i].content + prefix;
  6032.             break;
  6033.         } else {
  6034.             break;
  6035.         }
  6036.     }
  6037.     
  6038.     // learn glue
  6039.     for (i = pos + 1, il = tokens.length; i < il; i++) {
  6040.         if (tokens[i].type == 'white' || tokens[i].type == ':')
  6041.             glue += tokens[i].content;
  6042.         else break;
  6043.     }
  6044.     
  6045.     if (glue.indexOf(':') == -1)
  6046.         glue = ':';
  6047.     
  6048.     return function(name, value) {
  6049.         return prefix + name + glue + value + ';';
  6050.     };
  6051. }
  6052.  
  6053. /**
  6054.  * Returns image dimentions for source
  6055.  * @param {zen_editor} editor
  6056.  * @param {String} src Image source (path or data:url)
  6057.  */
  6058. function getImageSizeForSource(editor, src) {
  6059.     var f_content;
  6060.     if (src) {
  6061.         // check if it is data:url
  6062.         if (startsWith('data:', src)) {
  6063.             f_content = base64.decode( src.replace(/^data\:.+?;.+?,/, '') );
  6064.         } else {
  6065.             var abs_path = zen_file.locateFile(editor.getFilePath(), src);
  6066.             if (abs_path === null) {
  6067.                 throw "Can't find " + src + ' file';
  6068.             }
  6069.             
  6070.             f_content = String(zen_file.read(abs_path));
  6071.         }
  6072.         
  6073.         return zen_coding.getImageSize(f_content);
  6074.     }
  6075. }
  6076.  
  6077. /**
  6078.  * Find image tag under caret
  6079.  * @param {zen_editor} editor
  6080.  * @return Image tag and its indexes inside editor source
  6081.  */
  6082. function findImage(editor) {
  6083.     var caret_pos = editor.getCaretPos(),
  6084.         content = String(editor.getContent()),
  6085.         content_len = content.length,
  6086.         start_ix = -1,
  6087.         end_ix = -1;
  6088.     
  6089.     // find the beginning of the tag
  6090.     do {
  6091.         if (caret_pos < 0)
  6092.             break;
  6093.         if (content.charAt(caret_pos) == '<') {
  6094.             if (content.substring(caret_pos, caret_pos + 4).toLowerCase() == '<img') {
  6095.                 // found the beginning of the image tag
  6096.                 start_ix = caret_pos;
  6097.                 break;
  6098.             } else {
  6099.                 // found some other tag
  6100.                 return null;
  6101.             }
  6102.         }
  6103.     } while(caret_pos--);
  6104.     
  6105.     // find the end of the tag 
  6106.     caret_pos = editor.getCaretPos();
  6107.     do {
  6108.         if (caret_pos >= content_len)
  6109.             break;
  6110.             
  6111.         if (content.charAt(caret_pos) == '>') {
  6112.             end_ix = caret_pos + 1;
  6113.             break;
  6114.         }
  6115.     } while(caret_pos++);
  6116.     
  6117.     if (start_ix != -1 && end_ix != -1)
  6118.         
  6119.         return {
  6120.             start: start_ix,
  6121.             end: end_ix,
  6122.             tag: content.substring(start_ix, end_ix)
  6123.         };
  6124.     
  6125.     return null;
  6126. }
  6127.  
  6128. /**
  6129.  * Replaces or adds attribute to the tag
  6130.  * @param {String} img_tag
  6131.  * @param {String} attr_name
  6132.  * @param {String} attr_value
  6133.  */
  6134. function replaceOrAppend(img_tag, attr_name, attr_value) {
  6135.     if (img_tag.toLowerCase().indexOf(attr_name) != -1) {
  6136.         // attribute exists
  6137.         var re = new RegExp(attr_name + '=([\'"])(.*?)([\'"])', 'i');
  6138.         return img_tag.replace(re, function(str, p1, p2){
  6139.             return attr_name + '=' + p1 + attr_value + p1;
  6140.         });
  6141.     } else {
  6142.         return img_tag.replace(/\s*(\/?>)$/, ' ' + attr_name + '="' + attr_value + '" $1');
  6143.     }
  6144. }
  6145.  
  6146. function doCSSReflection(editor) {
  6147.     var content = String(editor.getContent()),
  6148.         caret_pos = editor.getCaretPos(),
  6149.         css = ParserUtils.extractCSSRule(content, caret_pos),
  6150.         v;
  6151.         
  6152.     if (!css || caret_pos < css[0] || caret_pos > css[1])
  6153.         // no matching CSS rule or caret outside rule bounds
  6154.         return false;
  6155.         
  6156.     var tokens = ParserUtils.parseCSS(content.substring(css[0], css[1]), css[0]),
  6157.         token_ix = findTokenFromPosition(tokens, caret_pos, 'identifier');
  6158.     
  6159.     if (token_ix != -1) {
  6160.         var cur_prop = tokens[token_ix].content,
  6161.             value_token = findValueToken(tokens, token_ix + 1),
  6162.             base_name = getBaseCSSName(cur_prop),
  6163.             re_name = new RegExp('^(?:\\-\\w+\\-)?' + base_name + '$'),
  6164.             re_name = getReflectedCSSName(base_name),
  6165.             values = [];
  6166.             
  6167.         if (!value_token) return false;
  6168.             
  6169.         // search for all vendor-prefixed properties
  6170.         for (var i = 0, token, il = tokens.length; i < il; i++) {
  6171.             token = tokens[i];
  6172.             if (token.type == 'identifier' && re_name.test(token.content) && token.content != cur_prop) {
  6173.                 v = findValueToken(tokens, i + 1);
  6174.                 if (v) 
  6175.                     values.push({name: token, value: v});
  6176.             }
  6177.         }
  6178.         
  6179.         // some editors do not provide easy way to replace multiple code 
  6180.         // fragments so we have to squash all replace operations into one
  6181.         if (values.length) {
  6182.             var data = content.substring(values[0].value.start, values[values.length - 1].value.end),
  6183.                 offset = values[0].value.start,
  6184.                 value = value_token.content,
  6185.                 rv;
  6186.                 
  6187.             for (var i = values.length - 1; i >= 0; i--) {
  6188.                 v = values[i].value;
  6189.                 rv = getReflectedValue(cur_prop, value, values[i].name.content, v.content);
  6190.                 data = replaceSubstring(data, v.start - offset, v.end - offset, rv);
  6191.                     
  6192.                 // also calculate new caret position
  6193.                 if (v.start < caret_pos) {
  6194.                     caret_pos += rv.length - v.content.length;
  6195.                 }
  6196.             }
  6197.             
  6198.             return {
  6199.                 'data': data,
  6200.                 'start': offset,
  6201.                 'end': values[values.length - 1].value.end,
  6202.                 'caret': caret_pos
  6203.             };
  6204.         }
  6205.     }
  6206. }
  6207.  
  6208. zen_coding.actions.doCSSReflection = doCSSReflection;
  6209.  
  6210. /**
  6211.  * Removes vendor prefix from CSS property
  6212.  * @param {String} name CSS property
  6213.  * @return {String}
  6214.  */
  6215. function getBaseCSSName(name) {
  6216.     return name.replace(/^\s*\-\w+\-/, '');
  6217. }
  6218.  
  6219. /**
  6220.  * Returns regexp that should match reflected CSS property names
  6221.  * @param {String} name Current CSS property name
  6222.  * @return {RegExp}
  6223.  */
  6224. function getReflectedCSSName(name) {
  6225.     name = getBaseCSSName(name);
  6226.     var vendor_prefix = '^(?:\\-\\w+\\-)?', m;
  6227.     
  6228.     if (name == 'opacity' || name == 'filter') {
  6229.         return new RegExp(vendor_prefix + '(?:opacity|filter)$');
  6230.     } else if (m = name.match(/^border-radius-(top|bottom)(left|right)/)) {
  6231.         // Mozilla-style border radius
  6232.         return new RegExp(vendor_prefix + '(?:' + name + '|border-' + m[1] + '-' + m[2] + '-radius)$');
  6233.     } else if (m = name.match(/^border-(top|bottom)-(left|right)-radius/)) { 
  6234.         return new RegExp(vendor_prefix + '(?:' + name + '|border-radius-' + m[1] + m[2] + ')$');
  6235.     }
  6236.     
  6237.     return new RegExp(vendor_prefix + name + '$');
  6238. }
  6239.  
  6240. /**
  6241.  * Returns value that should be reflected for <code>ref_name</code> CSS property
  6242.  * from <code>cur_name</code> property. This function is used for special cases,
  6243.  * when the same result must be achieved with different properties for different
  6244.  * browsers. For example: op╨░city:0.5; -> filter:alpha(opacity=50);<br><br>
  6245.  * 
  6246.  * This function does value conversion between different CSS properties
  6247.  * 
  6248.  * @param {String} cur_name Current CSS property name
  6249.  * @param {String} cur_value Current CSS property value
  6250.  * @param {String} ref_name Receiver CSS property's name 
  6251.  * @param {String} ref_value Receiver CSS property's value
  6252.  * @return {String} New value for receiver property
  6253.  */
  6254. function getReflectedValue(cur_name, cur_value, ref_name, ref_value) {
  6255.     cur_name = getBaseCSSName(cur_name);
  6256.     ref_name = getBaseCSSName(ref_name);
  6257.     
  6258.     if (cur_name == 'opacity' && ref_name == 'filter') {
  6259.         return ref_value.replace(/opacity=[^)]*/i, 'opacity=' + Math.floor(parseFloat(cur_value) * 100));
  6260.     } else if (cur_name == 'filter' && ref_name == 'opacity') {
  6261.         var m = cur_value.match(/opacity=([^)]*)/i);
  6262.         return m ? prettifyNumber(parseInt(m[1]) / 100) : ref_value;
  6263.     }
  6264.     
  6265.     return cur_value;
  6266. }
  6267.  
  6268. /**
  6269.  * Find value token, staring at <code>pos</code> index and moving right
  6270.  * @param {Array} tokens
  6271.  * @param {Number} pos
  6272.  * @return {ParserUtils.token}
  6273.  */
  6274. function findValueToken(tokens, pos) {
  6275.     for (var i = pos, il = tokens.length; i < il; i++) {
  6276.         var t = tokens[i];
  6277.         if (t.type == 'value')
  6278.             return t;
  6279.         else if (t.type == 'identifier' || t.type == ';')
  6280.             break;
  6281.     }
  6282.     
  6283.     return null;
  6284. }
  6285.  
  6286. /**
  6287.  * Replace substring of <code>text</code>, defined by <code>start</code> and 
  6288.  * <code>end</code> indexes with <code>new_value</code>
  6289.  * @param {String} text
  6290.  * @param {Number} start
  6291.  * @param {Number} end
  6292.  * @param {String} new_value
  6293.  * @return {String}
  6294.  */
  6295. function replaceSubstring(text, start, end, new_value) {
  6296.     return text.substring(0, start) + new_value + text.substring(end);
  6297. }
  6298.  
  6299. /**
  6300.  * Search for token with specified type left to the specified position
  6301.  * @param {Array} tokens List of parsed tokens
  6302.  * @param {Number} pos Position where to start searching
  6303.  * @param {String} type Token type
  6304.  * @return {Number} Token index
  6305.  */
  6306. function findTokenFromPosition(tokens, pos, type) {
  6307.     // find token under caret
  6308.     var token_ix = -1;
  6309.     for (var i = 0, il = tokens.length; i < il; i++) {
  6310.         var token = tokens[i];
  6311.         if (token.start <= pos && token.end >= pos) {
  6312.             token_ix = i;
  6313.             break;
  6314.         }
  6315.     }
  6316.     
  6317.     if (token_ix != -1) {
  6318.         // token found, search left until we find token with specified type
  6319.         while (token_ix >= 0) {
  6320.             if (tokens[token_ix].type == type)
  6321.                 return token_ix;
  6322.             token_ix--;
  6323.         }
  6324.     }
  6325.     
  6326.     return -1;
  6327. }
  6328.  
  6329. zen_coding.registerAction('reflect_css_value', reflectCSSValue);
  6330. zen_coding.registerAction('update_image_size', updateImageSize);/**
  6331.  * Actions that use stream parsers and tokenizers for traversing:
  6332.  * -- Search for next/previous items in HTML
  6333.  * -- Search for next/previous items in CSS
  6334.  * 
  6335.  * @author Sergey Chikuyonok (serge.che@gmail.com)
  6336.  * @link http://chikuyonok.ru
  6337.  * 
  6338.  * @include "../zen_editor.js"
  6339.  * @include "utils.js"
  6340.  * @include "stringstream.js"
  6341.  * @include "parsexml.js"
  6342.  * @include "tokenize.js"
  6343.  * @include "sex.js"
  6344.  * @include "parserutils.js"
  6345.  */
  6346. (function(){
  6347.     var start_tag = /^<([\w\:\-]+)((?:\s+[\w\-:]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/,
  6348.         known_xml_types = {
  6349.             'xml-tagname': 1,
  6350.             'xml-attname': 1,
  6351.             'xml-attribute': 1
  6352.         },
  6353.         known_css_types = {
  6354.             'selector': 1,
  6355.             'identifier': 1,
  6356.             'value': 1
  6357.         };
  6358.     
  6359.     /**
  6360.      * Find next HTML item
  6361.      * @param {zen_editor} editor
  6362.      */
  6363.     function findNextHTMLItem(editor) {
  6364.         var is_first = true;
  6365.         return findItem(editor, false, function(content, search_pos){
  6366.             if (is_first) {
  6367.                 is_first = false;
  6368.                 return findOpeningTagFromPosition(content, search_pos);
  6369.             } else {
  6370.                 return getOpeningTagFromPosition(content, search_pos);
  6371.             }
  6372.         }, getRangeForNextItemInHTML);
  6373.     }
  6374.     
  6375.     /**
  6376.      * Find previous HTML item
  6377.      * @param {zen_editor} editor
  6378.      */
  6379.     function findPrevHTMLItem(editor) {
  6380.         return findItem(editor, true, getOpeningTagFromPosition, getRangeForPrevItemInHTML);
  6381.     }
  6382.     
  6383.     /**
  6384.      * Returns range for item to be selected in tag after current caret position
  6385.      * @param {String} tag Tag declaration
  6386.      * @param {Number} offset Tag's position index inside content
  6387.      * @param {Number} sel_start Start index of user selection
  6388.      * @param {Number} sel_end End index of user selection
  6389.      * @return {Array} Returns array with two indexes if next item was found, 
  6390.      * <code>null</code> otherwise
  6391.      */
  6392.     function getRangeForNextItemInHTML(tag, offset, sel_start, sel_end) {
  6393.         var tokens = ParserUtils.parseHTML(tag, offset),
  6394.             next = [];
  6395.                 
  6396.         // search for token that is right to selection
  6397.         for (var i = 0, il = tokens.length; i < il; i++) {
  6398.             /** @type {ParserUtils.token} */
  6399.             var token = tokens[i], pos_test;
  6400.             if (token.type in known_xml_types) {
  6401.                 // check token position
  6402.                 pos_test = token.start >= sel_start;
  6403.                 if (token.type == 'xml-attribute' && isQuote(token.content.charAt(0)))
  6404.                     pos_test = token.start + 1 >= sel_start && token.end -1 != sel_end;
  6405.                 
  6406.                 if (!pos_test && !(sel_start == sel_end && token.end > sel_start)) continue;
  6407.                 
  6408.                 // found token that should be selected
  6409.                 if (token.type == 'xml-attname') {
  6410.                     next = handleFullAttributeHTML(tokens, i, sel_end <= token.end ? token.start : -1);
  6411.                     if (next) return next;
  6412.                 } else if (token.end > sel_end) {
  6413.                     next = [token.start, token.end];
  6414.                     
  6415.                     if (token.type == 'xml-attribute')
  6416.                         next = handleQuotesHTML(token.content, next);
  6417.                         
  6418.                     if (sel_start == next[0] && sel_end == next[1])
  6419.                         // in case of empty attribute
  6420.                         continue;
  6421.                     
  6422.                     return next;
  6423.                 }
  6424.             }
  6425.         }
  6426.         
  6427.         return null;
  6428.     }
  6429.     
  6430.     /**
  6431.      * Returns range for item to be selected in tag before current caret position
  6432.      * @param {String} tag Tag declaration
  6433.      * @param {Number} offset Tag's position index inside content
  6434.      * @param {Number} sel_start Start index of user selection
  6435.      * @param {Number} sel_end End index of user selection
  6436.      * @return {Array} Returns array with two indexes if next item was found, 
  6437.      * <code>null</code> otherwise
  6438.      */
  6439.     function getRangeForPrevItemInHTML(tag, offset, sel_start, sel_end) {
  6440.         var tokens = ParserUtils.parseHTML(tag, offset),
  6441.             next;
  6442.                 
  6443.         // search for token that is left to the selection
  6444.         for (var i = tokens.length - 1, il = tokens.length; i >= 0; i--) {
  6445.             /** @type {ParserUtils.token} */
  6446.             var token = tokens[i], pos_test;
  6447.             if (token.type in known_xml_types) {
  6448.                 // check token position
  6449.                 pos_test = token.start < sel_start;
  6450.                 if (token.type == 'xml-attribute' && isQuote(token.content.charAt(0))) {
  6451.                     pos_test = token.start + 1 < sel_start;
  6452.                 }
  6453.                 
  6454.                 if (!pos_test) continue;
  6455.                 
  6456.                 // found token that should be selected
  6457.                 if (token.type == 'xml-attname') {
  6458.                     next = handleFullAttributeHTML(tokens, i, token.start);
  6459.                     if (next) return next;
  6460.                 } else {
  6461.                     next = [token.start, token.end];
  6462.                     
  6463.                     if (token.type == 'xml-attribute')
  6464.                         next = handleQuotesHTML(token.content, next);
  6465.                     
  6466.                     return next;
  6467.                 }
  6468.             }
  6469.         }
  6470.         
  6471.         return null;
  6472.     }
  6473.     
  6474.     /**
  6475.      * Search for opening tag in content, starting at specified position
  6476.      * @param {String} html Where to search tag
  6477.      * @param {Number} pos Character index where to start searching
  6478.      * @return {Array} Returns array with tag indexes if valid opening tag was found,
  6479.      * <code>null</code> otherwise
  6480.      */
  6481.     function findOpeningTagFromPosition(html, pos) {
  6482.         var tag;
  6483.         while (pos >= 0) {
  6484.             if (tag = getOpeningTagFromPosition(html, pos))
  6485.                 return tag;
  6486.             pos--;
  6487.         }
  6488.         
  6489.         return null;
  6490.     }
  6491.     
  6492.     /**
  6493.      * @param {String} html Where to search tag
  6494.      * @param {Number} pos Character index where to start searching
  6495.      * @return {Array} Returns array with tag indexes if valid opening tag was found,
  6496.      * <code>null</code> otherwise
  6497.      */
  6498.     function getOpeningTagFromPosition(html, pos) {
  6499.         var m;
  6500.         if (html.charAt(pos) == '<' && (m = html.substring(pos, html.length).match(start_tag))) {
  6501.             return [pos, pos + m[0].length];
  6502.         }
  6503.     }
  6504.     
  6505.     function isQuote(ch) {
  6506.         return ch == '"' || ch == "'";
  6507.     }
  6508.     
  6509.     /**
  6510.      * Find item
  6511.      * @param {zen_editor} editor
  6512.      * @param {String} is_backward Search backward (search forward otherwise)
  6513.      * @param {Function} extract_fn Function that extracts item content
  6514.      * @param {Function} range_rn Function that search for next token range
  6515.      */
  6516.     function findItem(editor, is_backward, extract_fn, range_fn) {
  6517.         var content = String(editor.getContent()),
  6518.             c_len = content.length,
  6519.             item,
  6520.             item_def,
  6521.             rng,
  6522.             loop = 100000, // endless loop protection
  6523.             prev_range = [-1, -1],
  6524.             sel = editor.getSelectionRange(),
  6525.             sel_start = Math.min(sel.start, sel.end),
  6526.             sel_end = Math.max(sel.start, sel.end);
  6527.             
  6528.         var search_pos = sel_start;
  6529.         while (search_pos >= 0 && search_pos < c_len && loop > 0) {
  6530.             loop--;
  6531.             if ( (item = extract_fn(content, search_pos, is_backward)) ) {
  6532.                 if (prev_range[0] == item[0] && prev_range[1] == item[1]) {
  6533.                     break;
  6534.                 }
  6535.                 
  6536.                 prev_range[0] = item[0];
  6537.                 prev_range[1] = item[1];
  6538.                 item_def = content.substring(item[0], item[1]);
  6539.                 rng = range_fn(item_def, item[0], sel_start, sel_end);
  6540.                     
  6541.                 if (rng) {
  6542.                     editor.createSelection(rng[0], rng[1]);
  6543.                     return true;
  6544.                 } else {
  6545.                     search_pos = is_backward ? item[0] : item[1] - 1;
  6546.                 }
  6547.             }
  6548.             
  6549.             search_pos += is_backward ? -1 : 1;
  6550.         }
  6551.         
  6552.         return false;
  6553.     }
  6554.     
  6555.     function findNextCSSItem(editor) {
  6556.         return findItem(editor, false, ParserUtils.extractCSSRule, getRangeForNextItemInCSS);
  6557.     }
  6558.     
  6559.     function findPrevCSSItem(editor) {
  6560.         return findItem(editor, true, ParserUtils.extractCSSRule, getRangeForPrevItemInCSS);
  6561.     }
  6562.     
  6563.     /**
  6564.      * Returns range for item to be selected in tag after current caret position
  6565.      * @param {String} rule CSS rule declaration
  6566.      * @param {Number} offset Rule's position index inside content
  6567.      * @param {Number} sel_start Start index of user selection
  6568.      * @param {Number} sel_end End index of user selection
  6569.      * @return {Array} Returns array with two indexes if next item was found, 
  6570.      * <code>null</code> otherwise
  6571.      */
  6572.     function getRangeForNextItemInCSS(rule, offset, sel_start, sel_end) {
  6573.         var tokens = ParserUtils.parseCSS(rule, offset), pos_test,
  6574.             next = [];
  6575.             
  6576.         /**
  6577.          * Same range is used inside complex value processor
  6578.          * @return {Boolean}
  6579.          */
  6580.         function checkSameRange(r) {
  6581.             return r[0] == sel_start && r[1] == sel_end;
  6582.         }
  6583.                 
  6584.         // search for token that is right to selection
  6585.         for (var i = 0, il = tokens.length; i < il; i++) {
  6586.             /** @type {ParserUtils.token} */
  6587.             var token = tokens[i], pos_test;
  6588.             if (token.type in known_css_types) {
  6589.                 // check token position
  6590.                 if (sel_start == sel_end)
  6591.                     pos_test = token.end > sel_start;
  6592.                 else {
  6593.                     pos_test = token.start >= sel_start;
  6594.                     if (token.type == 'value') // respect complex values
  6595.                         pos_test = pos_test || sel_start >= token.start && token.end >= sel_end;
  6596.                 }
  6597.                 
  6598.                 if (!pos_test) continue;
  6599.                 
  6600.                 // found token that should be selected
  6601.                 if (token.type == 'identifier') {
  6602.                     var rule_sel = handleFullRuleCSS(tokens, i, sel_end <= token.end ? token.start : -1);
  6603.                     if (rule_sel) return rule_sel;
  6604.                     
  6605.                 } else if (token.type == 'value' && sel_end > token.start && token.children) {
  6606.                     // looks like a complex value
  6607.                     var children = token.children;
  6608.                     for (var j = 0, jl = children.length; j < jl; j++) {
  6609.                         if (children[j][0] >= sel_start || (sel_start == sel_end && children[j][1] > sel_start)) {
  6610.                             next = [children[j][0], children[j][1]];
  6611.                             if (checkSameRange(next)) {
  6612.                                 var rule_sel = handleCSSSpecialCase(rule, next[0], next[1], offset);
  6613.                                 if (!checkSameRange(rule_sel))
  6614.                                     return rule_sel;
  6615.                                 else
  6616.                                     continue;
  6617.                             }
  6618.                             
  6619.                             return next;
  6620.                         }
  6621.                     }
  6622.                 } else if (token.end > sel_end) {
  6623.                     return [token.start, token.end];
  6624.                 }
  6625.             }
  6626.         }
  6627.         
  6628.         return null;
  6629.     }
  6630.     
  6631.     /**
  6632.      * Returns range for item to be selected in CSS rule before current caret position
  6633.      * @param {String} rule CSS rule declaration
  6634.      * @param {Number} offset Rule's position index inside content
  6635.      * @param {Number} sel_start Start index of user selection
  6636.      * @param {Number} sel_end End index of user selection
  6637.      * @return {Array} Returns array with two indexes if next item was found, 
  6638.      * <code>null</code> otherwise
  6639.      */
  6640.     function getRangeForPrevItemInCSS(rule, offset, sel_start, sel_end) {
  6641.         var tokens = ParserUtils.parseCSS(rule, offset),
  6642.             next = [];
  6643.                 
  6644.         /**
  6645.          * Same range is used inside complex value processor
  6646.          * @return {Boolean}
  6647.          */
  6648.         function checkSameRange(r) {
  6649.             return r[0] == sel_start && r[1] == sel_end;
  6650.         }
  6651.             
  6652.         // search for token that is left to the selection
  6653.         for (var i = tokens.length - 1, il = tokens.length; i >= 0; i--) {
  6654.             /** @type {ParserUtils.token} */
  6655.             var token = tokens[i], pos_test;
  6656.             if (token.type in known_css_types) {
  6657.                 // check token position
  6658.                 pos_test = token.start < sel_start;
  6659.                 if (token.type == 'value' && token.ref_start_ix != token.ref_end_ix) // respect complex values
  6660.                     pos_test = token.start <= sel_start;
  6661.                 
  6662.                 if (!pos_test) continue;
  6663.                 
  6664.                 // found token that should be selected
  6665.                 if (token.type == 'identifier') {
  6666.                     var rule_sel = handleFullRuleCSS(tokens, i, token.start);
  6667.                     if (rule_sel) return rule_sel;
  6668.                 } else if (token.type == 'value' && token.ref_start_ix != token.ref_end_ix) {
  6669.                     // looks like a complex value
  6670.                     var children = token.children;
  6671.                     for (var j = children.length - 1; j >= 0; j--) {
  6672.                         if (children[j][0] < sel_start) {
  6673.                             // create array copy
  6674.                             next = [children[j][0], children[j][1]]; 
  6675.                             
  6676.                             var rule_sel = handleCSSSpecialCase(rule, next[0], next[1], offset);
  6677.                             return !checkSameRange(rule_sel) ? rule_sel : next;
  6678.                         }
  6679.                     }
  6680.                     
  6681.                     // if we are here than we already traversed trough all
  6682.                     // child tokens, select full value
  6683.                     next = [token.start, token.end];
  6684.                     if (!checkSameRange(next)) 
  6685.                         return next;
  6686.                 } else {
  6687.                     return [token.start, token.end];
  6688.                 }
  6689.             }
  6690.         }
  6691.         
  6692.         return null;
  6693.     }
  6694.     
  6695.     function handleFullRuleCSS(tokens, i, start) {
  6696.         for (var j = i + 1, il = tokens.length; j < il; j++) {
  6697.             /** @type {ParserUtils.token} */
  6698.             var _t = tokens[j];
  6699.             if ((_t.type == 'value' && start == -1) || _t.type == 'identifier') {
  6700.                 return [_t.start, _t.end];
  6701.             } else if (_t.type == ';') {
  6702.                 return [start == -1 ? _t.start : start, _t.end];
  6703.             } else if (_t.type == '}') {
  6704.                 return [start == -1 ? _t.start : start, _t.start - 1];
  6705.             }
  6706.         }
  6707.         
  6708.         return null;
  6709.     }
  6710.     
  6711.     function handleFullAttributeHTML(tokens, i, start) {
  6712.         for (var j = i + 1, il = tokens.length; j < il; j++) {
  6713.             /** @type {ParserUtils.token} */
  6714.             var _t = tokens[j];
  6715.             if (_t.type == 'xml-attribute') {
  6716.                 if (start == -1)
  6717.                     return handleQuotesHTML(_t.content, [_t.start, _t.end]);
  6718.                 else
  6719.                     return [start, _t.end];
  6720.             } else if (_t.type == 'xml-attname') {
  6721.                 // moved to next attribute, adjust selection
  6722.                 return [_t.start, tokens[i].end];
  6723.             }
  6724.         }
  6725.             
  6726.         return null;
  6727.     }
  6728.     
  6729.     function handleQuotesHTML(attr, r) {
  6730.         if (isQuote(attr.charAt(0)))
  6731.             r[0]++;
  6732.         if (isQuote(attr.charAt(attr.length - 1)))
  6733.             r[1]--;
  6734.             
  6735.         return r;
  6736.     }
  6737.     
  6738.     function handleCSSSpecialCase(text, start, end, offset) {
  6739.         text = text.substring(start - offset, end - offset);
  6740.         var m;
  6741.         if (m = text.match(/^[\w\-]+\(['"]?/)) {
  6742.             start += m[0].length;
  6743.             if (m = text.match(/['"]?\)$/))
  6744.                 end -= m[0].length;
  6745.         }
  6746.         
  6747.         return [start, end];
  6748.     }
  6749.     
  6750.     // XXX register actions 
  6751.     zen_coding.registerAction('select_next_item', function(/* zen_editor */ editor){
  6752.         if (editor.getSyntax() == 'css')
  6753.             return findNextCSSItem(editor);
  6754.         else
  6755.             return findNextHTMLItem(editor);
  6756.     });
  6757.     
  6758.     zen_coding.registerAction('select_previous_item', function(/* zen_editor */ editor){
  6759.         if (editor.getSyntax() == 'css')
  6760.             return findPrevCSSItem(editor);
  6761.         else
  6762.             return findPrevHTMLItem(editor);
  6763.     });
  6764. })();/**
  6765.  * Comment important tags (with 'id' and 'class' attributes)
  6766.  * @author Sergey Chikuyonok (serge.che@gmail.com)
  6767.  * @link http://chikuyonok.ru
  6768.  */(function(){
  6769.     /**
  6770.      * Add comments to tag
  6771.      * @param {ZenNode} node
  6772.      */
  6773.     function addComments(node, i) {
  6774.         var id_attr = node.getAttribute('id'),
  6775.             class_attr = node.getAttribute('class'),
  6776.             nl = zen_coding.getNewline();
  6777.             
  6778.         if (id_attr || class_attr) {
  6779.             var comment_str = '',
  6780.                 padding = (node.parent) ? node.parent.padding : '';
  6781.             if (id_attr) comment_str += '#' + id_attr;
  6782.             if (class_attr) comment_str += '.' + class_attr;
  6783.             
  6784.             node.start = node.start.replace(/</, '<!-- ' + comment_str + ' -->' + nl + padding + '<');
  6785.             node.end = node.end.replace(/>/, '>' + nl + padding + '<!-- /' + comment_str + ' -->');
  6786.             
  6787.             // replace counters
  6788.             var counter = zen_coding.getCounterForNode(node);
  6789.             node.start = zen_coding.replaceCounter(node.start, counter);
  6790.             node.end = zen_coding.replaceCounter(node.end, counter);
  6791.         }
  6792.     }
  6793.     
  6794.     function process(tree, profile) {
  6795.         if (profile.tag_nl === false)
  6796.             return tree;
  6797.             
  6798.         for (var i = 0, il = tree.children.length; i < il; i++) {
  6799.             /** @type {ZenNode} */
  6800.             var item = tree.children[i];
  6801.             
  6802.             if (item.isBlock())
  6803.                 addComments(item, i);
  6804.             
  6805.             process(item, profile);
  6806.         }
  6807.         
  6808.         return tree;
  6809.     }
  6810.     
  6811.     zen_coding.registerFilter('c', process);
  6812. })();/**
  6813.  * Process CSS properties: replaces snippets, augumented with ! char, with 
  6814.  * <em>!important</em> suffix 
  6815.  * @author Sergey Chikuyonok (serge.che@gmail.com)
  6816.  * @link http://chikuyonok.ru
  6817.  */(function(){
  6818.     var re_important = /(.+)\!$/;
  6819.     function process(tree, profile) {
  6820.         for (var i = 0, il = tree.children.length; i < il; i++) {
  6821.             /** @type {ZenNode} */
  6822.             var item = tree.children[i];
  6823.             
  6824.             // CSS properties are always snippets
  6825.             if (item.type == 'snippet' && re_important.test(item.real_name)) {
  6826.                 item.start = item.start.replace(/(;?)$/, ' !important$1');
  6827.             }
  6828.             
  6829.             process(item, profile);
  6830.         }
  6831.         
  6832.         return tree;
  6833.     }
  6834.     
  6835.     zen_coding.registerFilter('css', process);
  6836. })();/**
  6837.  * Filter for escaping unsafe XML characters: <, >, &
  6838.  * @author Sergey Chikuyonok (serge.che@gmail.com)
  6839.  * @link http://chikuyonok.ru
  6840.  */(function(){
  6841.     var char_map = {
  6842.         '<': '<',
  6843.         '>': '>',
  6844.         '&': '&'
  6845.     }
  6846.     
  6847.     function escapeChars(str) {
  6848.         return str.replace(/([<>&])/g, function(str, p1){
  6849.             return char_map[p1];
  6850.         });
  6851.     }
  6852.     
  6853.     function process(tree, profile, level) {
  6854.         for (var i = 0, il = tree.children.length; i < il; i++) {
  6855.             /** @type {ZenNode} */
  6856.             var item = tree.children[i];
  6857.             
  6858.             item.start = escapeChars(item.start);
  6859.             item.end = escapeChars(item.end);
  6860.             
  6861.             process(item);
  6862.         }
  6863.         
  6864.         return tree;
  6865.     }
  6866.     
  6867.     zen_coding.registerFilter('e', process);
  6868. })();/**
  6869.  * Format CSS properties: add space after property name:
  6870.  * padding:0; ΓåÆ padding: 0;
  6871.  * @author Sergey Chikuyonok (serge.che@gmail.com)
  6872.  * @link http://chikuyonok.ru
  6873.  */(function(){
  6874.     function process(tree, profile) {
  6875.         for (var i = 0, il = tree.children.length; i < il; i++) {
  6876.             /** @type {ZenNode} */
  6877.             var item = tree.children[i];
  6878.             
  6879.             // CSS properties are always snippets 
  6880.             if (item.type == 'snippet') {
  6881.                 item.start = item.start.replace(/([\w\-]+\s*:)(?!:)\s*/, '$1 ');
  6882.             }
  6883.             
  6884.             process(item, profile);
  6885.         }
  6886.         
  6887.         return tree;
  6888.     }
  6889.     
  6890.     zen_coding.registerFilter('fc', process);
  6891. })();/**
  6892.  * Generic formatting filter: creates proper indentation for each tree node,
  6893.  * placing "%s" placeholder where the actual output should be. You can use
  6894.  * this filter to preformat tree and then replace %s placeholder to whatever you
  6895.  * need. This filter should't be called directly from editor as a part 
  6896.  * of abbreviation.
  6897.  * @author Sergey Chikuyonok (serge.che@gmail.com)
  6898.  * @link http://chikuyonok.ru
  6899.  * 
  6900.  * @include "../zen_coding.js"
  6901.  */(function(){
  6902.     var child_token = '${child}',
  6903.         placeholder = '%s';
  6904.     
  6905.     function getNewline() {
  6906.         return zen_coding.getNewline();
  6907.     }
  6908.     
  6909.     function getIndentation() {
  6910.         return zen_resources.getVariable('indentation');
  6911.     }
  6912.     
  6913.     /**
  6914.      * Test if passed node has block-level sibling element
  6915.      * @param {ZenNode} item
  6916.      * @return {Boolean}
  6917.      */
  6918.     function hasBlockSibling(item) {
  6919.         return (item.parent && item.parent.hasBlockChildren());
  6920.     }
  6921.     
  6922.     /**
  6923.      * Test if passed itrem is very first child of the whole tree
  6924.      * @param {ZenNode} tree
  6925.      */
  6926.     function isVeryFirstChild(item) {
  6927.         return item.parent && !item.parent.parent && !item.previousSibling;
  6928.     }
  6929.     
  6930.     /**
  6931.      * Need to add line break before element
  6932.      * @param {ZenNode} node
  6933.      * @param {Object} profile
  6934.      * @return {Boolean}
  6935.      */
  6936.     function shouldBreakLine(node, profile) {
  6937.         if (!profile.inline_break)
  6938.             return false;
  6939.             
  6940.         // find toppest non-inline sibling
  6941.         while (node.previousSibling && node.previousSibling.isInline())
  6942.             node = node.previousSibling;
  6943.         
  6944.         if (!node.isInline())
  6945.             return false;
  6946.             
  6947.         // calculate how many inline siblings we have
  6948.         var node_count = 1;
  6949.         while (node = node.nextSibling) {
  6950.             if (node.type == 'text' || !node.isInline())
  6951.                 node_count = 0;
  6952.             else if (node.isInline())
  6953.                 node_count++;
  6954.         }
  6955.         
  6956.         return node_count >= profile.inline_break;
  6957.     }
  6958.     
  6959.     /**
  6960.      * Need to add newline because <code>item</code> has too many inline children
  6961.      * @param {ZenNode} node
  6962.      * @param {Object} profile
  6963.      */
  6964.     function shouldBreakChild(node, profile) {
  6965.         // we need to test only one child element, because 
  6966.         // hasBlockChildren() method will do the rest
  6967.         return (node.children.length && shouldBreakLine(node.children[0], profile));
  6968.     }
  6969.     
  6970.     /**
  6971.      * Processes element with <code>snippet</code> type
  6972.      * @param {ZenNode} item
  6973.      * @param {Object} profile
  6974.      * @param {Number} [level] Depth level
  6975.      */
  6976.     function processSnippet(item, profile, level) {
  6977.         var data = item.source.value;
  6978.             
  6979.         if (!data)
  6980.             // snippet wasn't found, process it as tag
  6981.             return processTag(item, profile, level);
  6982.             
  6983.         item.start = item.end = placeholder;
  6984.         
  6985.         var padding = (item.parent) 
  6986.             ? item.parent.padding
  6987.             : zen_coding.repeatString(getIndentation(), level);
  6988.         
  6989.         if (!isVeryFirstChild(item)) {
  6990.             item.start = getNewline() + padding + item.start;
  6991.         }
  6992.         
  6993.         // adjust item formatting according to last line of <code>start</code> property
  6994.         var parts = data.split(child_token),
  6995.             lines = zen_coding.splitByLines(parts[0] || ''),
  6996.             padding_delta = getIndentation();
  6997.             
  6998.         if (lines.length > 1) {
  6999.             var m = lines[lines.length - 1].match(/^(\s+)/);
  7000.             if (m)
  7001.                 padding_delta = m[1];
  7002.         }
  7003.         
  7004.         item.padding = padding + padding_delta;
  7005.         
  7006.         return item;
  7007.     }
  7008.     
  7009.     /**
  7010.      * Processes element with <code>tag</code> type
  7011.      * @param {ZenNode} item
  7012.      * @param {Object} profile
  7013.      * @param {Number} [level] Depth level
  7014.      */
  7015.     function processTag(item, profile, level) {
  7016.         if (!item.name)
  7017.             // looks like it's a root element
  7018.             return item;
  7019.         
  7020.         item.start = item.end = placeholder;
  7021.         
  7022.         var is_unary = (item.isUnary() && !item.children.length);
  7023.             
  7024.         // formatting output
  7025.         if (profile.tag_nl !== false) {
  7026.             var padding = (item.parent) 
  7027.                     ? item.parent.padding
  7028.                     : zen_coding.repeatString(getIndentation(), level),
  7029.                 force_nl = (profile.tag_nl === true),
  7030.                 should_break = shouldBreakLine(item, profile);
  7031.             
  7032.             // formatting block-level elements
  7033.             if (item.type != 'text') {
  7034.                 if (( (item.isBlock() || should_break) && item.parent) || force_nl) {
  7035.                     // snippet children should take different formatting
  7036.                     if (!item.parent || (item.parent.type != 'snippet' && !isVeryFirstChild(item)))
  7037.                         item.start = getNewline() + padding + item.start;
  7038.                         
  7039.                     if (item.hasBlockChildren() || shouldBreakChild(item, profile) || (force_nl && !is_unary))
  7040.                         item.end = getNewline() + padding + item.end;
  7041.                         
  7042.                     if (item.hasTagsInContent() || (force_nl && !item.hasChildren() && !is_unary))
  7043.                         item.start += getNewline() + padding + getIndentation();
  7044.                     
  7045.                 } else if (item.isInline() && hasBlockSibling(item) && !isVeryFirstChild(item)) {
  7046.                     item.start = getNewline() + padding + item.start;
  7047.                 } else if (item.isInline() && item.hasBlockChildren()) {
  7048.                     item.end = getNewline() + padding + item.end;
  7049.                 }
  7050.                 
  7051.                 item.padding = padding + getIndentation();
  7052.             }
  7053.         }
  7054.         
  7055.         return item;
  7056.     }
  7057.     
  7058.     /**
  7059.      * Processes simplified tree, making it suitable for output as HTML structure
  7060.      * @param {ZenNode} tree
  7061.      * @param {Object} profile
  7062.      * @param {Number} [level] Depth level
  7063.      */
  7064.     function process(tree, profile, level) {
  7065.         level = level || 0;
  7066.         
  7067.         for (var i = 0, il = tree.children.length; i < il; i++) {
  7068.             /** @type {ZenNode} */
  7069.             var item = tree.children[i];
  7070.             item = (item.type == 'tag') 
  7071.                 ? processTag(item, profile, level) 
  7072.                 : processSnippet(item, profile, level);
  7073.                 
  7074.             if (item.content)
  7075.                 item.content = zen_coding.padString(item.content, item.padding);
  7076.                 
  7077.             process(item, profile, level + 1);
  7078.         }
  7079.         
  7080.         return tree;
  7081.     }
  7082.     
  7083.     zen_coding.registerFilter('_format', process);
  7084. })();/**
  7085.  * Filter that produces HAML tree
  7086.  * @author Sergey Chikuyonok (serge.che@gmail.com)
  7087.  * @link http://chikuyonok.ru
  7088.  * 
  7089.  * @include "../zen_coding.js"
  7090.  */
  7091. (function(){
  7092.     var child_token = '${child}';
  7093.     
  7094.     /**
  7095.      * Creates HTML attributes string from tag according to profile settings
  7096.      * @param {ZenNode} tag
  7097.      * @param {default_profile} profile
  7098.      */
  7099.     function makeAttributesString(tag, profile) {
  7100.         // make attribute string
  7101.         var attrs = '',
  7102.             attr_quote = profile.attr_quotes == 'single' ? "'" : '"',
  7103.             cursor = profile.place_cursor ? zen_coding.getCaretPlaceholder() : '',
  7104.             attr_name, 
  7105.             i,
  7106.             a;
  7107.             
  7108.         // use short notation for ID and CLASS attributes
  7109.         for (i = 0; i < tag.attributes.length; i++) {
  7110.             a = tag.attributes[i];
  7111.             switch (a.name.toLowerCase()) {
  7112.                 case 'id':
  7113.                     attrs += '#' + (a.value || cursor);
  7114.                     break;
  7115.                 case 'class':
  7116.                     attrs += '.' + (a.value || cursor);
  7117.                     break;
  7118.             }
  7119.         }
  7120.         
  7121.         var other_attrs = [];
  7122.         
  7123.         // process other attributes
  7124.         for (i = 0; i < tag.attributes.length; i++) {
  7125.             a = tag.attributes[i];
  7126.             var attr_name_lower = a.name.toLowerCase();
  7127.             if (attr_name_lower != 'id' && attr_name_lower != 'class') {
  7128.                 attr_name = (profile.attr_case == 'upper') ? a.name.toUpperCase() : attr_name_lower;
  7129.                 other_attrs.push(':' +attr_name + ' => ' + attr_quote + (a.value || cursor) + attr_quote);
  7130.             }
  7131.         }
  7132.         
  7133.         if (other_attrs.length)
  7134.             attrs += '{' + other_attrs.join(', ') + '}';
  7135.         
  7136.         return attrs;
  7137.     }
  7138.     
  7139.     /**
  7140.      * Processes element with <code>snippet</code> type
  7141.      * @param {ZenNode} item
  7142.      * @param {Object} profile
  7143.      * @param {Number} [level] Depth level
  7144.      */
  7145.     function processSnippet(item, profile, level) {
  7146.         var data = item.source.value;
  7147.             
  7148.         if (!data)
  7149.             // snippet wasn't found, process it as tag
  7150.             return processTag(item, profile, level);
  7151.             
  7152.         var parts = data.split(child_token),
  7153.             start = parts[0] || '',
  7154.             end = parts[1] || '',
  7155.             padding = item.parent ? item.parent.padding : '';
  7156.             
  7157.         item.start = item.start.replace('%s', zen_coding.padString(start, padding));
  7158.         item.end = item.end.replace('%s', zen_coding.padString(end, padding));
  7159.         
  7160.         // replace variables ID and CLASS
  7161.         var cb = function(str, var_name) {
  7162.             if (var_name == 'id' || var_name == 'class')
  7163.                 return item.getAttribute(var_name);
  7164.             else
  7165.                 return str;
  7166.         };
  7167.         item.start = zen_coding.replaceVariables(item.start, cb);
  7168.         item.end = zen_coding.replaceVariables(item.end, cb);
  7169.         
  7170.         return item;
  7171.     }
  7172.     
  7173.     /**
  7174.      * Test if passed node has block-level sibling element
  7175.      * @param {ZenNode} item
  7176.      * @return {Boolean}
  7177.      */
  7178.     function hasBlockSibling(item) {
  7179.         return (item.parent && item.parent.hasBlockChildren());
  7180.     }
  7181.     
  7182.     /**
  7183.      * Processes element with <code>tag</code> type
  7184.      * @param {ZenNode} item
  7185.      * @param {Object} profile
  7186.      * @param {Number} [level] Depth level
  7187.      */
  7188.     function processTag(item, profile, level) {
  7189.         if (!item.name)
  7190.             // looks like it's root element
  7191.             return item;
  7192.         
  7193.         var attrs = makeAttributesString(item, profile), 
  7194.             content = '', 
  7195.             cursor = profile.place_cursor ? zen_coding.getCaretPlaceholder() : '',
  7196.             self_closing = '',
  7197.             is_unary = (item.isUnary() && !item.children.length),
  7198.             start= '',
  7199.             end = '';
  7200.         
  7201.         if (profile.self_closing_tag && is_unary)
  7202.             self_closing = '/';
  7203.             
  7204.         // define tag name
  7205.         var tag_name = '%' + ((profile.tag_case == 'upper') ? item.name.toUpperCase() : item.name.toLowerCase());
  7206.         if (tag_name.toLowerCase() == '%div' && attrs && attrs.indexOf('{') == -1)
  7207.             // omit div tag
  7208.             tag_name = '';
  7209.             
  7210.         item.end = '';
  7211.         start = tag_name + attrs + self_closing;
  7212.         
  7213.         var placeholder = '%s';
  7214.         // We can't just replace placeholder with new value because
  7215.         // JavaScript will treat double $ character as a single one, assuming
  7216.         // we're using RegExp literal. 
  7217.         var pos = item.start.indexOf(placeholder);
  7218.         item.start = item.start.substring(0, pos) + start + item.start.substring(pos + placeholder.length);
  7219.         
  7220.         if (!item.children.length && !is_unary)
  7221.             item.start += cursor;
  7222.         
  7223.         return item;
  7224.     }
  7225.     
  7226.     /**
  7227.      * Processes simplified tree, making it suitable for output as HTML structure
  7228.      * @param {ZenNode} tree
  7229.      * @param {Object} profile
  7230.      * @param {Number} [level] Depth level
  7231.      */
  7232.     function process(tree, profile, level) {
  7233.         level = level || 0;
  7234.         if (level == 0)
  7235.             // preformat tree
  7236.             tree = zen_coding.runFilters(tree, profile, '_format');
  7237.         
  7238.         for (var i = 0, il = tree.children.length; i < il; i++) {
  7239.             /** @type {ZenNode} */
  7240.             var item = tree.children[i];
  7241.             item = (item.type == 'tag') 
  7242.                 ? processTag(item, profile, level) 
  7243.                 : processSnippet(item, profile, level);
  7244.             
  7245.             // replace counters
  7246.             var counter = zen_coding.getCounterForNode(item);
  7247.             item.start = zen_coding.unescapeText(zen_coding.replaceCounter(item.start, counter));
  7248.             item.end = zen_coding.unescapeText(zen_coding.replaceCounter(item.end, counter));
  7249.             
  7250.             process(item, profile, level + 1);
  7251.         }
  7252.         
  7253.         return tree;
  7254.     }
  7255.     
  7256.     zen_coding.registerFilter('haml', process);
  7257. })();/**
  7258.  * Filter that produces HTML tree
  7259.  * @author Sergey Chikuyonok (serge.che@gmail.com)
  7260.  * @link http://chikuyonok.ru
  7261.  * 
  7262.  * @include "../zen_coding.js"
  7263.  */
  7264. (function(){
  7265.     var child_token = '${child}',
  7266.         tabstops = 0;
  7267.         
  7268.     /**
  7269.      * Returns proper string case, depending on profile value
  7270.      * @param {String} val String to process
  7271.      * @param {String} case_param Profile's case value ('lower', 'upper', 'leave')
  7272.      */
  7273.     function processStringCase(val, case_param) {
  7274.         switch (String(case_param || '').toLowerCase()) {
  7275.             case 'lower':
  7276.                 return val.toLowerCase();
  7277.             case 'upper':
  7278.                 return val.toUpperCase();
  7279.         }
  7280.         
  7281.         return val;
  7282.     }
  7283.     
  7284.     /**
  7285.      * Creates HTML attributes string from tag according to profile settings
  7286.      * @param {ZenNode} tag
  7287.      * @param {default_profile} profile
  7288.      */
  7289.     function makeAttributesString(tag, profile) {
  7290.         // make attribute string
  7291.         var attrs = '',
  7292.             attr_quote = profile.attr_quotes == 'single' ? "'" : '"',
  7293.             cursor = profile.place_cursor ? zen_coding.getCaretPlaceholder() : '',
  7294.             attr_name;
  7295.             
  7296.         for (var i = 0; i < tag.attributes.length; i++) {
  7297.             var a = tag.attributes[i];
  7298.             attr_name = processStringCase(a.name, profile.attr_case);
  7299.             attrs += ' ' + attr_name + '=' + attr_quote + (a.value || cursor) + attr_quote;
  7300.         }
  7301.         
  7302.         return attrs;
  7303.     }
  7304.     
  7305.     /**
  7306.      * Processes element with <code>snippet</code> type
  7307.      * @param {ZenNode} item
  7308.      * @param {Object} profile
  7309.      * @param {Number} [level] Depth level
  7310.      */
  7311.     function processSnippet(item, profile, level) {
  7312.         var data = item.source.value;
  7313.             
  7314.         if (!data)
  7315.             // snippet wasn't found, process it as tag
  7316.             return processTag(item, profile, level);
  7317.             
  7318.         var parts = data.split(child_token),
  7319.             start = parts[0] || '',
  7320.             end = parts[1] || '',
  7321.             padding = item.parent ? item.parent.padding : '';
  7322.             
  7323.             
  7324.         item.start = item.start.replace('%s', zen_coding.padString(start, padding));
  7325.         item.end = item.end.replace('%s', zen_coding.padString(end, padding));
  7326.         
  7327.         // replace variables ID and CLASS
  7328.         var cb = function(str, var_name) {
  7329.             if (var_name == 'id' || var_name == 'class')
  7330.                 return item.getAttribute(var_name);
  7331.             else
  7332.                 return str;
  7333.         };
  7334.         item.start = zen_coding.replaceVariables(item.start, cb);
  7335.         item.end = zen_coding.replaceVariables(item.end, cb);
  7336.         
  7337.         return item;
  7338.     }
  7339.     
  7340.     /**
  7341.      * Test if passed node has block-level sibling element
  7342.      * @param {ZenNode} item
  7343.      * @return {Boolean}
  7344.      */
  7345.     function hasBlockSibling(item) {
  7346.         return (item.parent && item.parent.hasBlockChildren());
  7347.     }
  7348.     
  7349.     /**
  7350.      * Processes element with <code>tag</code> type
  7351.      * @param {ZenNode} item
  7352.      * @param {Object} profile
  7353.      * @param {Number} [level] Depth level
  7354.      */
  7355.     function processTag(item, profile, level) {
  7356.         if (!item.name)
  7357.             // looks like it's root element
  7358.             return item;
  7359.         
  7360.         var attrs = makeAttributesString(item, profile), 
  7361.             content = '', 
  7362.             cursor = profile.place_cursor ? zen_coding.getCaretPlaceholder() : '',
  7363.             self_closing = '',
  7364.             is_unary = (item.isUnary() && !item.children.length),
  7365.             start= '',
  7366.             end = '';
  7367.         
  7368.         if (profile.self_closing_tag == 'xhtml')
  7369.             self_closing = ' /';
  7370.         else if (profile.self_closing_tag === true)
  7371.             self_closing = '/';
  7372.             
  7373.         // define opening and closing tags
  7374.         if (item.type != 'text') {
  7375.             var tag_name = processStringCase(item.name, profile.tag_case);
  7376.             if (is_unary) {
  7377.                 start = '<' + tag_name + attrs + self_closing + '>';
  7378.                 item.end = '';
  7379.             } else {
  7380.                 start = '<' + tag_name + attrs + '>';
  7381.                 end = '</' + tag_name + '>';
  7382.             }
  7383.         }
  7384.         
  7385.         var placeholder = '%s';
  7386.         // We can't just replace placeholder with new value because
  7387.         // JavaScript will treat double $ character as a single one, assuming
  7388.         // we're using RegExp literal. 
  7389.         var pos = item.start.indexOf(placeholder);
  7390.         item.start = item.start.substring(0, pos) + start + item.start.substring(pos + placeholder.length);
  7391.         
  7392.         pos = item.end.indexOf(placeholder);
  7393.         item.end = item.end.substring(0, pos) + end + item.end.substring(pos + placeholder.length);
  7394.         
  7395.         if (!item.children.length && !is_unary && item.content.indexOf(cursor) == -1)
  7396.             item.start += cursor;
  7397.         
  7398.         return item;
  7399.     }
  7400.     
  7401.     /**
  7402.      * Processes simplified tree, making it suitable for output as HTML structure
  7403.      * @param {ZenNode} tree
  7404.      * @param {Object} profile
  7405.      * @param {Number} [level] Depth level
  7406.      */
  7407.     function process(tree, profile, level) {
  7408.         level = level || 0;
  7409.         if (level == 0) {
  7410.             tree = zen_coding.runFilters(tree, profile, '_format');
  7411.             tabstops = 0;
  7412.         }
  7413.         
  7414.         for (var i = 0, il = tree.children.length; i < il; i++) {
  7415.             /** @type {ZenNode} */
  7416.     
  7417.             var item = tree.children[i];
  7418.             item = (item.type == 'tag') 
  7419.                 ? processTag(item, profile, level) 
  7420.                 : processSnippet(item, profile, level);
  7421.             
  7422.             // replace counters
  7423.             var counter = zen_coding.getCounterForNode(item);
  7424.             item.start = zen_coding.unescapeText(zen_coding.replaceCounter(item.start, counter));
  7425.             item.end = zen_coding.unescapeText(zen_coding.replaceCounter(item.end, counter));
  7426.             item.content = zen_coding.unescapeText(zen_coding.replaceCounter(item.content, counter));
  7427.             
  7428.             tabstops += zen_coding.upgradeTabstops(item, tabstops) + 1;
  7429.             
  7430.             process(item, profile, level + 1);
  7431.         }
  7432.         
  7433.         return tree;
  7434.     }
  7435.     
  7436.     zen_coding.registerFilter('html', process);
  7437. })();/**
  7438.  * Output abbreviation on a single line (i.e. no line breaks)
  7439.  * @author Sergey Chikuyonok (serge.che@gmail.com)
  7440.  * @link http://chikuyonok.ru
  7441.  */
  7442. (function(){
  7443.     function process(tree, profile, level) {
  7444.         for (var i = 0, il = tree.children.length; i < il; i++) {
  7445.             /** @type {ZenNode} */
  7446.             var item = tree.children[i];
  7447.             if (item.type == 'tag') {
  7448.                 // remove padding from item 
  7449.                 var re_pad = /^\s+/;
  7450.                 item.start = item.start.replace(re_pad, '');
  7451.                 item.end = item.end.replace(re_pad, '');
  7452.             }
  7453.             
  7454.             // remove newlines 
  7455.             var re_nl = /[\n\r]/g;
  7456.             item.start = item.start.replace(re_nl, '');
  7457.             item.end = item.end.replace(re_nl, '');
  7458.             item.content = item.content.replace(re_nl, '');
  7459.             
  7460.             process(item);
  7461.         }
  7462.         
  7463.         return tree;
  7464.     }
  7465.     
  7466.     zen_coding.registerFilter('s', process);
  7467. })();/**
  7468.  * Trim filter: removes characters at the beginning of the text
  7469.  *  content that indicates lists: numbers, #, *, -, etc.
  7470.  * @author Sergey Chikuyonok (serge.che@gmail.com)
  7471.  * @link http://chikuyonok.ru
  7472.  */
  7473. (function(){
  7474.     function process(tree, profile, level) {
  7475.         for (var i = 0, il = tree.children.length; i < il; i++) {
  7476.             /** @type {ZenNode} */
  7477.             var item = tree.children[i];
  7478.             
  7479.             if (item.content)
  7480.                 item.content = item.content.replace(/^([\s|\u00a0])?[\d|#|\-|\*|\u2022]+\.?\s*/, '$1');
  7481.             
  7482.             process(item);
  7483.         }
  7484.         
  7485.         return tree;
  7486.     }
  7487.     
  7488.     zen_coding.registerFilter('t', process);
  7489. })();/**
  7490.  * Filter for trimming "select" attributes from some tags that contains
  7491.  * child elements
  7492.  * @author Sergey Chikuyonok (serge.che@gmail.com)
  7493.  * @link http://chikuyonok.ru
  7494.  */(function(){
  7495.     var tags = {
  7496.         'xsl:variable': 1,
  7497.         'xsl:with-param': 1
  7498.     };
  7499.     
  7500.     /**
  7501.      * Removes "select" attribute from node
  7502.      * @param {ZenNode} node
  7503.      */
  7504.     function trimAttribute(node) {
  7505.         node.start = node.start.replace(/\s+select\s*=\s*(['"]).*?\1/, '');
  7506.     }
  7507.     
  7508.     function process(tree) {
  7509.         for (var i = 0, il = tree.children.length; i < il; i++) {
  7510.             /** @type {ZenNode} */
  7511.             var item = tree.children[i];
  7512.             if (item.type == 'tag' && item.name.toLowerCase() in tags && item.children.length)
  7513.                 trimAttribute(item);
  7514.             process(item);
  7515.         }
  7516.     }
  7517.     
  7518.     zen_coding.registerFilter('xsl', process);
  7519. })();/**
  7520.  * High-level editor interface that communicates with underlying editor (like 
  7521.  * TinyMCE, CKEditor, etc.) or browser.
  7522.  * Basically, you should call <code>zen_editor.setContext(obj)</code> method to
  7523.  * set up undelying editor context before using any other method.
  7524.  * 
  7525.  * This interface is used by <i>zen_actions.js</i> for performing different 
  7526.  * actions like <b>Expand abbreviation</b>  
  7527.  * 
  7528.  * @example
  7529.  * var textarea = document.getElemenetsByTagName('textarea')[0];
  7530.  * zen_editor.setContext(textarea);
  7531.  * //now you are ready to use editor object
  7532.  * zen_editor.getSelectionRange();
  7533.  * 
  7534.  * @author Sergey Chikuyonok (serge.che@gmail.com)
  7535.  * @link http://chikuyonok.ru
  7536.  */
  7537. var zen_editor = {
  7538.     context: null,
  7539.     /**
  7540.      * Setup underlying editor context. You should call this method 
  7541.      * <code>before</code> using any Zen Coding action.
  7542.      * @param {Object} context
  7543.      */
  7544.     setContext: function(context) {
  7545.         this.context = context;
  7546.  
  7547.         // get indentation characters
  7548.         if (this.context.useTabs) {
  7549.             indentation = '\t';
  7550.         } else {
  7551.             // using core zen_coding.repeatString() method to repeat string as many 
  7552.             // times as we need: zen_coding.repeatString('a', 4) -> 'aaaa'
  7553.             indentation = zen_coding.repeatString(' ', this.context.tabSize);
  7554.         }
  7555.  
  7556.         // setup ZC core
  7557.         zen_coding.setVariable('indentation', indentation);
  7558.         zen_coding.setNewline('\r\n');
  7559.     },
  7560.     
  7561.     /**
  7562.      * Returns character indexes of selected text: object with <code>start</code>
  7563.      * and <code>end</code> properties. If there's no selection, should return 
  7564.      * object with <code>start</code> and <code>end</code> properties referring
  7565.      * to current caret position
  7566.      * @return {Object}
  7567.      * @example
  7568.      * var selection = zen_editor.getSelectionRange();
  7569.      * alert(selection.start + ', ' + selection.end); 
  7570.      */
  7571.     getSelectionRange: function() {
  7572.         var selectionStart;
  7573.         var selectionEnd;
  7574.  
  7575.         if (this.context.hasSel)
  7576.         {
  7577.             var pos = this.context.pos;
  7578.             var anchor = this.context.anchor;
  7579.             if (pos >= anchor)
  7580.             {
  7581.                 selectionStart = anchor;
  7582.                 selectionEnd = pos;
  7583.             }
  7584.             else
  7585.             {
  7586.                 selectionStart = pos;
  7587.                 selectionEnd = anchor;
  7588.             }
  7589.         }
  7590.         else
  7591.             selectionStart = selectionEnd = this.getCaretPos();
  7592.  
  7593.         return {
  7594.             start: selectionStart,
  7595.             end: selectionEnd
  7596.         };
  7597.     },
  7598.     
  7599.     /**
  7600.      * Creates selection from <code>start</code> to <code>end</code> character
  7601.      * indexes. If <code>end</code> is ommited, this method should place caret 
  7602.      * and <code>start</code> index
  7603.      * @param {Number} start
  7604.      * @param {Number} [end]
  7605.      * @example
  7606.      * zen_editor.createSelection(10, 40);
  7607.      * 
  7608.      * //move caret to 15th character
  7609.      * zen_editor.createSelection(15);
  7610.      */
  7611.     createSelection: function(start, end) {
  7612.         this.context.setSel(start, end);
  7613.     },
  7614.     
  7615.     /**
  7616.      * Returns current line's start and end indexes as object with <code>start</code>
  7617.      * and <code>end</code> properties
  7618.      * @return {Object}
  7619.      * @example
  7620.      * var range = zen_editor.getCurrentLineRange();
  7621.      * alert(range.start + ', ' + range.end);
  7622.      */
  7623.     getCurrentLineRange: function() {
  7624.         var lineNumber = this.context.lineNumber;
  7625.         var lineStart = this.context.lineIndex(lineNumber);
  7626.         var lineEnd = lineStart + this.context.lineLength(lineNumber);
  7627.  
  7628.         return {
  7629.             start: lineStart, 
  7630.             end: lineEnd
  7631.         };
  7632.     },
  7633.     
  7634.     /**
  7635.      * Returns current caret position
  7636.      * @return {Number|null}
  7637.      */
  7638.     getCaretPos: function() {
  7639.         return this.context.pos;
  7640.     },
  7641.     
  7642.     /**
  7643.      * Set new caret position
  7644.      * @param {Number} pos Caret position
  7645.      */
  7646.     setCaretPos: function(pos) {
  7647.         this.context.setSel(pos, pos);
  7648.     },
  7649.     
  7650.     /**
  7651.      * Returns content of current line
  7652.      * @return {String}
  7653.      */
  7654.     getCurrentLine: function() {
  7655.         return this.context.line;
  7656.     },
  7657.     
  7658.     /**
  7659.      * Replace editor's content or it's part (from <code>start</code> to 
  7660.      * <code>end</code> index). If <code>value</code> contains 
  7661.      * <code>caret_placeholder</code>, the editor will put caret into 
  7662.      * this position. If you skip <code>start</code> and <code>end</code>
  7663.      * arguments, the whole target's content will be replaced with 
  7664.      * <code>value</code>. 
  7665.      * 
  7666.      * If you pass <code>start</code> argument only,
  7667.      * the <code>value</code> will be placed at <code>start</code> string 
  7668.      * index of current content. 
  7669.      * 
  7670.      * If you pass <code>start</code> and <code>end</code> arguments,
  7671.      * the corresponding substring of current target's content will be 
  7672.      * replaced with <code>value</code>. 
  7673.      * @param {String} value Content you want to paste
  7674.      * @param {Number} [start] Start index of editor's content
  7675.      * @param {Number} [end] End index of editor's content
  7676.      * @param {Boolean} [no_indent] Do not auto indent <code>value</code>
  7677.      */
  7678.     replaceContent: function(value, start, end, no_indent) {
  7679.         var has_start = typeof(start) !== 'undefined',
  7680.             has_end = typeof(end) !== 'undefined';
  7681.  
  7682.         // indent new value
  7683.         if (!no_indent)
  7684.             value = zen_coding.padString(value, this.getStringPadding(this.getCurrentLine()));
  7685.         
  7686.         // find new caret position
  7687.         var tabstop_res = this.handleTabStops(value);
  7688.         value = tabstop_res[0];
  7689.         
  7690.         start = start || 0;
  7691.         if (tabstop_res[1] !== -1) {
  7692.             tabstop_res[1] += start;
  7693.             tabstop_res[2] += start;
  7694.         } else {
  7695.             tabstop_res[1] = tabstop_res[2] = value.length + start;
  7696.         }
  7697.         
  7698.         try {
  7699.             var selectionStart, selectionEnd;
  7700.  
  7701.             if (has_start && has_end) {
  7702.                 selectionStart = start;
  7703.                 selectionEnd = end;
  7704.             } else if (has_start) {
  7705.                 selectionStart = selectionEnd = start;
  7706.             }
  7707.  
  7708.             this.context.setSel(selectionStart, selectionEnd);
  7709.             this.context.replaceSel(value, 1);
  7710.  
  7711.             this.createSelection(tabstop_res[1], tabstop_res[2]);
  7712.         } catch(e){}
  7713.     },
  7714.     
  7715.     /**
  7716.      * Returns editor's content
  7717.      * @return {String}
  7718.      */
  7719.     getContent: function() {
  7720.         return this.context.text;
  7721.     },
  7722.     
  7723.     /**
  7724.      * Returns current editor's syntax mode
  7725.      * @return {String}
  7726.      */
  7727.     getSyntax: function() {
  7728.         return this.context.syntaxScope.toLowerCase();
  7729.     },
  7730.     
  7731.     /**
  7732.      * Returns current output profile name (@see zen_coding#setupProfile)
  7733.      * @return {String}
  7734.      */
  7735.     getProfileName: function() {
  7736.         return this.context.profile;
  7737.     },
  7738.     
  7739.     /**
  7740.      * Ask user to enter something
  7741.      * @param {String} title Dialog title
  7742.      * @return {String} Entered data
  7743.      * @since 0.65
  7744.      */
  7745.     prompt: function(title) {
  7746.         return this.context.prompt(title);
  7747.     },
  7748.     
  7749.     /**
  7750.      * Returns current selection
  7751.      * @return {String}
  7752.      * @since 0.65
  7753.      */
  7754.     getSelection: function() {
  7755.         var sel = getSelectionRange();
  7756.         if (sel) {
  7757.             try {
  7758.                 return getContent().substring(sel.start, sel.end);
  7759.             } catch(e) {}
  7760.         }
  7761.         
  7762.         return '';
  7763.     },
  7764.     
  7765.     /**
  7766.      * Returns current editor's file path
  7767.      * @return {String}
  7768.      * @since 0.65 
  7769.      */
  7770.     getFilePath: function() {
  7771.         return '';
  7772.     },
  7773.  
  7774.     /**
  7775.      * Handle tab-stops (like $1 or ${1:label}) inside text: find first tab-stop,
  7776.      * marks it as selection, remove the rest. If tab-stop wasn't found, search
  7777.      * for caret placeholder and use it as selection
  7778.      * @param {String} text
  7779.      * @return {Array} Array with new text and selection indexes (['...', -1,-1] 
  7780.      * if there's no selection)
  7781.      */
  7782.     handleTabStops: function(text) {
  7783.         var selection_len = 0,
  7784.             caret_placeholder = zen_coding.getCaretPlaceholder(),
  7785.             caret_pos = text.indexOf(caret_placeholder),
  7786.             placeholders = {};
  7787.             
  7788.         // find caret position
  7789.         if (caret_pos != -1) {
  7790.             text = text.split(caret_placeholder).join('');
  7791.         } else {
  7792.             caret_pos = text.length;
  7793.         }
  7794.         
  7795.         text = zen_coding.processTextBeforePaste(text, 
  7796.             function(ch){ return ch; }, 
  7797.             function(i, num, val) {
  7798.                 if (val) placeholders[num] = val;
  7799.                 
  7800.                 if (i < caret_pos) {
  7801.                     caret_pos = i;
  7802.                     if (val)
  7803.                         selection_len = val.length;
  7804.                 }
  7805.                     
  7806.                 return placeholders[num] || '';
  7807.             });
  7808.         
  7809.         return [text, caret_pos, caret_pos + selection_len];
  7810.     },
  7811.  
  7812.     /**
  7813.      * Returns whitrespace padding of string
  7814.      * @param {String} str String line
  7815.      * @return {String}
  7816.      */
  7817.     getStringPadding: function(str) {
  7818.         return (str.match(/^(\s+)/) || [''])[0];
  7819.     }
  7820. };
  7821.  
  7822. var zen_controller = {
  7823.     runAction: function(action_name) {
  7824.         zen_editor.setContext(epp.currentView);
  7825.         zen_coding.runAction(action_name, zen_editor);
  7826.     }
  7827. }
  7828.